
Не так давно мы закончили писать первую версию документации по смарт-процессам и новому API CRM. Спасибо Роберту и его команде за оперативную публикацию.
Документация тут - и
Если её внимательно прочитать (а ещё лучше - ознакомиться с исходным кодом), то станет понятно, что у разработчиков появляется возможность внедриться очень глубоко в API "малой кровью" (без модификации ядра). Мы почти не ставили final у классов и методов, только местами.
Однако наличие этой возможности не значит, что ей необходимо очень активно пользоваться.
Как вы знаете, мы поддерживаем обратную совместимость. Но цена этой поддержки - довольно высока. Это в буквальном смысле сковывает нам руки - нам всё время приходится держать в голове необходимость поддержания совместимости, рефакторить сильно нельзя и т.д. Код становится "закостенелым" и буквально превращается в легаси с момента выхода первой его версии.
Теперь, когда у разработчиков есть возможность наследовать классы продукта, это становится ещё сложнее. Т.к. по-хорошему, мы не можем менять даже сигнатуру protected методов.
В связи с этим большая просьба - при наследовании классов придерживайтесь рекомендаций, изложенных на этой странице
Здесь я изложу их ещё раз немного другими словами
1. Не надо наследовать стандартные классы без особой необходимости вообще. Делайте это только в крайнем случае и когда вы полностью отдаете себе отчет, как и что вы делаете
2. Не надо использовать / наследовать те классы, которые в документации помечены соответствующим образом (ParentFieldManager и другие).
3. Если вы наследуете класс, ни в коем случае не меняйте сигнатуру методов добавлением новых аргументов / изменением типов возвращаемых значений и т.д. Если надо передать какие-то дополнительные данные, сделайте отдельный метод для этого
4. Не стоит слишком полагаться на структуру массивов внутри классов. При использовании делайте проверки.
5. Даже следуя этим рекомендациям можно столкнуться с тем, что после установки обновления что-то отвалится из-за каких-то изменений. Пишите тесты, тестируйте обновления не на продакшн-окружении, используйте фича-флаги.
Если после ознакомления с кодом или документацией вам что-то не понравилось - не стесняйтесь писать.
По поводу нового API для старых сущностей - будет, но не сразу. Не так давно мы сделали возможность читать данные о лидах, сделках, контактах и компаниях (выйдет в районе crm 21.800.0). Позже добавим возможность и делать изменения через новое API.
По поводу реста - доделываем прямо сейчас, будет классно.
Вопрос: когда можно ожидать инструмент импорта-экспорта элементов СП? 👀
В crm 21.800.0 выйдет рест. Возможно, кто-то запилит приложение с экспортом/импортом
Остаётся надеяться, что кто-нибудь из авторов имеющихся импортилок/экспортилок CRM-сущностей допилят свои детища до СП. 👀
А как обстоят дела с подтягиванием данных из СП в Аналитику и конструктор Отчётов? Есть надежда? 🤔
И ещё одна хотела — — так же, как других Сущностей CRM.
С конструктором отчетов - пока в планах не стоит. Может быть когда-нибудь и появится.
Пробуем на функционале СмП решить некоторые свои задачи учета, возникла такая потребность.
Вводные:
к СмП1 (перечень) привязан дочерний СмП2.
СмП1 не имеет привязки к клиенту.
СмП2 имеет привязку к клиенту плюс некоторые пользовательские поля.
Пользовательские поля СмП2 меняются в зависимости от продвижения сделки по клиенту, в сделке есть поле тип СмП1.
Задача: Обработать элемент СмП2 связанный с СмП1 и с клиентом
Вариант1: через getItem накладываем фильтр по клиентскому полю и проверяем связь СмП1 с СмП2
Вариант2: через getChildElements получаем дочерние элементы СмП1 и проверяем тип дочернего элемента = СмП2 и Клиент = Клиент
Вариант3: у СмП2 делаем пользовательское поле тип СмП1 и по нему вручную пишем в RelationManager, затем через getItem фильтруем по клиентскому полю и пользовательскому полю с Смп1
Все варианты некрасивые)
А вот getChildElements с фильтром красиво.
Пока что единственный рабочий вариант - это через getChildElements получить список идентификаторов и засунуть их в фильтр.
Это универсальное апи, оно очень простое, для решения узкой задачи.
Если вы заранее знаете тип связи (вплоть до ид типа), то воспользуйтесь прямым обращением к таблице \Bitrix\Crm\Relation\EntityRelationTable
function getChildElementByTypeWithFilter($parent, $childEntityTypeID, $arFilter = []){ $funcGetEntityId = function($curChildId): int {return $curChildId->getEntityId(); }; $funcGetItemIdentifier = function($curItem): Bitrix\Crm\ItemIdentifier {return new Bitrix\Crm\ItemIdentifier($curItem->getEntityTypeID(), $curItem->getID());}; $childEntity = Bitrix\Crm\Relation\StorageStrategy\EntityRelationTable::getChildElements($parent, $childEntityTypeID); $arRes = array_map ($funcGetEntityId,$childEntity); $arFilter[ID] = $arRes; $arParams=['filter'=>$arFilter]; $factory = \Bitrix\Crm\Service\Container::getInstance()->getFactory($childEntityTypeID); $items = $factory->getItems($arParams); $arResItem = array_map ($funcGetItemIdentifier,$items); return $arResItem; } $companyId = 97; $parentEntityTypeID = 128; $parentEntityID = 9; $childEntityTypeID = 179; $parent = new Bitrix\Crm\ItemIdentifier($parentEntityTypeID, $parentEntityID); $arFilter = ['COMPANY_ID'=>$companyId]; $a = getChildElementByTypeWithFilter($parent, $childEntityTypeID, $arFilter);Обнаглею и задам следующий вопрос, продублирую на форуме:
При интерактивном добавлении дочернего смарт-процесса(СмП), на его форме родительский элемент виден.
Но через бизнес-процессы доступа к нему нет, более того при попытке поискать через relationmanager получаем нулевой результат.
Видимо в EntityRelationTable данные пишутся уже после вызова всех обработчиков.
Нужно либо в шаблон БП "при создании" передавать идентифер родительского объекта, либо в EntityRelationTable писать до вызова пользовательского БП.
1. Сейчас сохранение связей для смарт-процессов осуществляется после выполнения Operation, и на уровне Item\Operation к значениям полей с родителями никак не добраться. Это поведение будет исправлено (в Item появится нативная поддержка таких полей, в Factory::getFieldsInfo будут данные о полях с родителями).
2. Когда будет исправлен пункт 1 появится возможность работать с этими полями и в БП (может быть не сразу, как коллеги запилят и выпустят).
Пока что да, родительские поля пристегнуты немного сбоку, ещё доделываем
Ещё было бы неплохо иметь возможность пристегивать шаблон БП при удалении элемента.
Смарт-процессы как раз удобны своей связанностью, но при этом при удалении родительского или дочернего элемента нет штатной возможности обработать связанные элементы.
Связанный элемент теряет связь и даже не знает об этом ничего.
Можно конечно подвесить БП с ожиданием и проверкой наличия связи, но это неправильно с точки зрения нагрузки на сервер.
Есть подобное в планах или какие-то другие варианты решения?
Скоро выйдет рест, там можно будет подписаться на событие удаление элемента смарт-процесса любого типа и внутри делать нужные вам проверки.
Кстати, в описан Action ACTION_AFTER_SAVE куда передается объект Item, который на момент ACTION_AFTER_SAVE уже удален, но в примере идет чтение его id, что на этот момент возвращает 0.
У меня вопрос по примеру "сделать только для чтения".
Все хорошо, если назначить полю атрибут \CCrmFieldInfoAttr::ReadOnly поле при сохранении не меняется, но зачем для такого поля в интерфейсе вообще давать возможность выбора? Это только путает пользователя.
В идеале такое поле должно отображаться на форме, но не иметь диалога выбора, только для чтения же.
Мне кажется это поведение нужно исправить.
Пользовательские поля в карточке отрисовываются "особым" образом, и в их представлении нет такого понятия, как "только для чтения".
По поводу элемента. С одной из версий crm во всех ACTION_AFTER_SAVE будет возможность получить инстанс Item до сохранения через \Bitrix\Crm\Service\Operation\Action::getItemBeforeSave(). Там будет id
Будет ли работать подписка? У меня не получается.
BX.addCustomEvent("onCrmDynamicItemAdd", BX.delegate(function(params){
console.log('onCrmDynamicItemAdd params',params);
}, this));
У меня CRM-форма, которая создает элемент смарт-процесса. Нужно получить id и его модифицировать (добавить товары и проч).
Ситуация следующая:
Сотрудник-Клиент ставит задачу на выполнение услуги, далее руководитель или менеджер двигают эту задачу по стадиям.
При этом Сотрудник-Клиент должен видеть (в идеале на некоторых стадиях вносить правки или хотя бы комментировать) движение его задачи.
Сейчас приходится костылить и постановщика принудительно записывать в наблюдатели.
Я говорю про коробку, обновления установлены актуальные.
Вариант с наблюдателями, мне кажется, вполне приемлем.
задачи".Рассмотрим следующую ситуацию:
Сотрудник компании (бухгалтер) нуждается в услуге по перевозке и подписи своих документов.
Движение из офиса1 в офис2, при этом в офисе2 надо поставить визу на документы и вернуть в офис1, дополненные другой пачкой. На выходе должны получить документ с историей согласования, номером регистрации в офисе2 и т.д.
Тут получаем стадии:
- запрос на перевозку,
- в пути в офис2,
- зарегистрировано в канцелярии офиса2,
- документы на подписи,
- в очереди на перевозку в офис1,
- в пути в офис1,
- закрыта.
Как быть бухгалтеру? Он должен видеть полное движение своих документов, где и на какой стадии, что застряло, если что позвонить и поторопить.RPA не подходит, процитирую Вас же "RPA это про процесс, который отработал и забыли, а СП это про документ", может не совсем дословно..
Это и не про документооборот, и не про задачу, это отлично складывается в СП со своими БП, стадии, связанными сущностями (по движению документов можно эскалировать создание других СП, задач, событий в календаре и т.д.)
Расширение ролей в смарт-процессах произойдёт только с реализацией новой схемы прав во всей CRM. Когда-нибудь это произойдет.
В общем, ждем. Было бы круто это получить совместно с множественными ответственными.
Можно какое-то пример кода для api?
В БП доступ появится вместе с релизом или...
Например, в новом апи
$item->set('PARENT_ID_2', 10);
$item->get('PARENT_ID_2');
в новом апи в $arFields можно читать / передавать поля вида PAREN_ID_...
Не работает.
И по идее getFieldsInfo должен что-то знать про это поле, правильно?
Какая версия crm стоит?
В методе \Bitrix\Crm\Service\Factory::getFieldsInfo() вызывается \Bitrix\Crm\Service\ParentFieldManager::getParentFieldsInfo(), который отдает родительские поля.
Проверьте, что у вас родитель и потомок не перепутаны местами.
Ну и можно дернуть метод \Bitrix\Crm\Service\ParentFieldManager::getParentFieldsInfo(), посмотреть, что отдает
array(1) { ["PARENT_ID_133"]=> array(3) { ["TYPE"]=> string(10) "crm_entity" ["SETTINGS"]=> array(1) { ["parentEntityTypeId"]=> int(133) } ["TITLE"]=> string(16) "Задачи" } }
Если смотреть ответ метода getParentFieldsInfo, то код выше и не должен ничего возращать. Т.к. у вас есть родительское поле PARENT_ID_133, а не PARENT_ID_161.
Entity has no `PARENT_ID_161` field.
Entity has no `PARENT_ID_133` field.
После обновления появилось новое активити "Информация о привязанном элементе" это способ доступа к обещанному родительскому полю?
Все хорошо, но активити работает через getParentElements, но в момент создания элемента связи ещё не прописаны и активити в БП при создании ничего не возвращает.
В коробке не работает.
Информация о привязанном элементе - в БП при создании элемента в доп.результате 0
[$entityTypeId, $entityId] = CCrmBizProcHelper::resolveEntityId($this->GetDocumentId());
$this->SetVariable("entityTypeId",$entityTypeId);
Что за ситуация, когда он вам понадобился? Шаблон БП жестко привязан к смарт-процессу, поэтому entityTypeId известен разработчику и в конкретном шаблоне это как константа.
Миграция однотипных шаблонов между сП?
В БП webhook дергает php-код на внешнем сервере, который, после необходимых операций записывает результат в текущий документ смарт-процесса (crm.item.update).
При вызове в webhook передаю entityTypeId смарт-процесса и id документа смарт-процесса. Думал что может есть какая-то языковая конструкция (типа как ID бизнес-процесса {=Workflow:ID})
Результат выполнения передается в переменную БП entityTypeId вот этим кодом:
$this->SetVariable("entityTypeId",$entityTypeId);
Посмотрите , вдруг пригодится
Через дебаг определил название таблицы
Но стандартный метод навешивания не работает
Мне нужно проверить и отфильтровать некоторые поля на изменение - можно конечно скопировать компонент crm.item.details и изменить метод saveAction - но хочется сделать "правильно"
<?php use Bitrix\Main\EventManager; use Bitrix\Main\Entity; use CRM_TestovyyTable; \Bitrix\Main\Diag\Debug::writeToFile(CRM_TestovyyTable::class . '::onBeforeUpdate', 'INIT'); $eventManager = EventManager::getInstance(); $eventManager->addEventHandler( 'crm', //CRM_TestovyyTable::class . '::onBeforeUpdate', '\Bitrix\Crm\Model\Dynamic\PrototypeItem::onBeforeUpdate', function (Entity\Event $event) { \Bitrix\Main\Diag\Debug::writeToFile('TEST', __METHOD__); $result = new Entity\EventResult(); global $USER; if ($USER) { \Bitrix\Main\Diag\Debug::writeToFile($USER->GetID(), __METHOD__); $data = $event->getParameter('fields'); \Bitrix\Main\Diag\Debug::writeToFile($data, 'data'); //if (isset($data['ISBN'])) //{ //$result->unsetFields(array('ISBN')); //} } return $result; } );не хватает примера как подменить/расширить класс таблицы сущности чтоб переопределить методы onBeforeUpdate или навесить обработчики без переопределения класса
Пример навешивания события на таблет есть вот тут \Bitrix\Crm\Service\Operation::launch() в конце
$eventType = $this->item->getEntityEventName('OnAfterUpdate'); $eventId = EventManager::getInstance()->addEventHandler( 'crm', $eventType, [$this, 'updateItemFromUpdateEvent'] );т е просто "сбросить" значения некоторых поля которые недоступны по моим настройкам на редактирование для определенных пользователей
подскажите dev.1c-bitrix.ru/api_d7/bitrix/crm/customization/index.php
если надо вмешатся в процесс изменения полей (при сохранении)
Я закинул себе в беклог задачу с добавлением событий в старом формате и для элементов смарт-процессов. Но ничего не могу сказать по срокам. Будут новости - напишу ещё заметку, наверное.
только он странно работает, поле в карточке все равно можно отредактировать хотя оно и не сохранится
и этот способ будет работать только для редактирования стандартной карточки документа или везде где производится модификация документа от текущего пользователя (через какие то другие компоненты или api смарт процессов)?
class MyFactory extends Service\Factory\Dynamic { public function getUpdateOperation(Item $item, Context $context = null): Operation\Update { $operation = parent::getUpdateOperation($item, $context); return $operation->addAction(В этом методе лучше добавлять свои Operation\Action, которые будут что-то менять во время выполнения
Если я правильно понял логику, то при изменении основного поля очищаются зависимые поля?
В интерфейсе отображаемые поля не всегда соответствуют полям в базе. Например, поле "Клиент" - это "Компания" + "Коллекция контактов".
В настройках задается обязательность поля "Клиент", а через этот метод проверяется, какие на самом деле надо проверять обязательные поля.
Будут множественные отвественные?))
Немного нестандартный вопрос.
Есть задача сделать КП с несколькими(множеством) цен за разные объемы покупки, т.е. нужно реализовать генератор документов, чтобы получить примерно такую таблицу
Наименование / Цена за 10шт / Цена за 100шт / Цена за 1000шт
Товар1 / 3р / / 2р
Товар2 / 5р / 4р /
т.е. в товары предложения можно добавить строки:
Товар1 10шт 3р
Товар1 1000шт 2р
Товар2 10шт 5р
Товар2 1000шт 4р
Кастомизацией провайдера планирую переопределять Products - повторяющиеся строки удалять, а цены и количество из них писать в первую строку, например, в пользовательское поле ProductsProductProperty57 или в собственное поле для плейсхолдера Products
В итоге получать типа такого:
Товар1 ProductsProductProperty55 - 10шт, ProductsProductProperty56 - 100шт, ProductsProductProperty57 - 1000шт, ProductsProductProperty58 - 3р, ProductsProductProperty59 - пусто, ProductsProductProperty60 - 2р
Товар2 ProductsProductProperty55 - 10шт, ProductsProductProperty56 - 100шт, ProductsProductProperty57 - 1000шт, ProductsProductProperty58 - 5р, ProductsProductProperty59 - 4р, ProductsProductProperty60 - пусто
Собственно проблема в том, что количество колонок может быть произвольное, в одном КП две колонки, в другом три или 4ре, а может быть даже 5.
Заранее закладывать максимальное количество колонок, значит в КП где их меньше будут некрасивые пустые колонки.
Сделать меньшее количество колонок, рано или поздно потребуется на одну больше.
В идеале вложенный цикл по колонкам нужен, но насколько я понимаю шаблоны такого не поддерживают.
Может быть есть какая-то более элегантная реализация?
Генератор документов не поддерживает сложную работу с таблицами - объединение колонок, произвольное количесто колонок и т.д.
Чтобы решить эту задачу, придется переопределить парсер \Bitrix\DocumentGenerator\Body\DocxXml, и в нем сделать полностью свой механизм формирования сложных таблиц на осное ваших данных. Провайдера тоже надо будет добавить, который будет вам данные готовить в нужном виде.
Был у меня небольшой подход к такой задаче, в конце файла \Bitrix\DocumentGenerator\Body\DocxXml есть несколько закоментированных методов, можно туда глянуть.
Для чего изменили генератор STATUS_ID?
Раннее он был по шаблону
'DT' + {entityTypeId} + '_' + {categoryId} + ':' + {порядковый номер} = DT155_29:2Сейчас
'DT' + {entityTypeId} + '_' + {categoryId} + ':' + {уникальная строка} = DT155_29:UC_TAPCXXЕсли создавать несколько направлений в рамках одного СП, то можно легко встраивать свои проверки перед сохранением в кастомной фабрике, главное условие - создавать стадии в том же порядке.
if ( $item->isChangedStageId() && $item->getStageId() === 'DT'.$type.'_'.$category.':2' && $item->remindActual(Item::FIELD_NAME_STAGE_ID) !== 'DT'.$type.'_'.$category.':NEW' ) { $result->addError(new Error('Переход в данную стадию невозможен'); }Сейчас приходится лезть в БД и напрямую там исправлять.
А еще это всё помножим на отсутствие миграции с тестовой машины в рабочую, вот и получаем много монотонной работы на ровном месте.
У меня есть СП с 30 воронками, в каждой по 3-6 кастомных стадий (помимо дефолтных)
Сделано это было по запросу разработчиков (лично мне не мешало).
Запрос был в том, что при удалении и создании новой стадии код этих стадий совпадал. И это могло мешать при интеграциях.
Мы понимаем, что это могло поломать какие-то другие сценарии.
Для решения проблемы есть несколько способов
1. Создавать стадии через апи, задавая свои коды
2. Подписаться на соответствующее событие onBeforeAdd таблета и там делать замену на то, что нужно
На сейчас оба варианта не быстрее, чем в базе поменять
Если вдруг появится желание и время, то было бы круто рядом с кнопкой заливки цветом заиметь вторую кнопку с заменой кода на свой - это упростило бы жизнь простым пользователям, кто в код и базу не может влезть.
Столкнулась с проблемой при работе со смарт-процессами в разных Б24. Прошу вашей помощи.
Есть выгрузка из 1С документов в смарт-процесс облачного Б24. Все замечательно. При выгрузке выполняется связь элемента смарт-процесса с сущностью Сделки. ИД сделки записывается в поле parentID2. Связи появляются, все работает.
При выполнении этого же обмена с коробочной версией Б24 столкнулась с тем, что поле parentID* не появляется при указании связи в смарт-процессе.
Версия коробочной битрикс 21.900
Имеется задача по смарт-процессу, чтобы при смене стадии назад или смене на определенную стадию смарт-процесс возвращался обратно на стадию. То есть сделать запрет на передвижение смарт-процесса назад, разрешить только вперед.
Не знаю, как реализовать для смарт-процесса подобную логику. Как я понимаю, надо делать через новое API так, как там имеются функции для смарт-процессов. Мне источники и примеры куда копать, чтобы реализовать данную логику. И подскажите реально реализовать подобную логику, чтобы нельзя было назад двигать смарт-процесс?
Б24 коробка.
Имеется код для сделок:
AddEventHandler("crm", "OnBeforeCrmDealUpdate", "OnBeforeCrmDealUpdate"); function OnBeforeCrmDealUpdate( &$arFields) { global $USER; $curUser = $USER->GetID(); //получаем текущую сделку до изменения //в массиве arFields у нас будут данные по этой сделке, для обновления $curDeal = \CCrmDeal::GetList(['DATE_CREATE' => 'DESC'],["ID"=>$arFields['ID'], 'CHECK_PERMISSIONS' => 'N'],[],false)->fetch(); if($curUser == 5871) // 9 = ID пользователя которому не нужно давать менять стадию { //если текущая стадия сделки равна, допустим "Новая сделка", та самая по которой нам нужно для пользователя откатывать назад, иначе ничего не делаем if($curDeal['STAGE_ID'] == 'C5:NEW') { $arFields['STAGE_ID'] = $curDeal['STAGE_ID']; //откатываем сделку назад } } }Если подходит, то данный код также пишется в init, как в события?
Исходя из примеров пытаюсь создать обработку событий для смарт-счетов (entity_type_id = 31). Через подмену фабрики. Примерно так:
class CrmContainer extends Service\Container { public function getFactory(int $entityTypeId): ?Service\Factory { if ($entityTypeId == 31) { $type = $this->getTypeByEntityTypeId($entityTypeId); $factory = new class($type) extends Service\Factory\Dynamic { // тут уже переопределение методов конкретного действия // ... }; return $factory; } return parent::getFactory($entityTypeId); } }Ловлю ошибку на детальной странице счета