Добавление пользовательского перехода для нового элемента кампании
Glossary Item Box
Общие сведения
Для настройки маркетинговой кампании используется [Дизайнер кампании]. С его помощью можно создать визуальную схему кампании, состоящую из связанных предустановленных элементов. Также существует возможность создания пользовательских элементов кампании.
Общая последовательность добавления пользовательского перехода (стрелки):
- Создание новой схемы для элемента [Переход].
- Создание страницы редактирования свойств элемента [Переход].
- Создание серверной части элемента [Переход].
- Создание исполняемого элемента для перехода.
- Создание замещающего модуля CampaignConnectorManager для добавления логики работы перехода.
- Подключение замещающего модуля CampaignConnectorManager.
Описание примера
Необходимо создать кастомный переход (стрелку) из нового элемента кампании для отправки СМС сообщений пользователем, в котором добавить возможность выбора условия отклика по рассылке. В карточке настройки пользователь может выбрать опцию не учитывать отклики, либо учитывать с перечнем возможных откликов по рассылке. Если не выбран ни один отклик, переводить для любого значения отклика.
ВАЖНО
В этом примере в названиях схем отсутствует префикс Usr. Префикс, используемый по умолчанию, можно изменить в системной настройке [Префикс названия объекта] (код SchemaNamePrefix).
ВАЖНО
Перед выполнением примера выполните шаги 1-6 из статьи "Добавление пользовательского элемента кампании".
Исходный код
Пакет с реализацией примера можно скачать по ссылке.
Алгоритм выполнения примера
1. Создание объектов [Получатель СМС] и [Отклики по рассылке]
Для полноценной работы примера в пакете разработки создайте схемы объектов [Получатель СМС] ([TestSmsTarget]) (рис. 1) и [Отклики по рассылке] ([TestSmsResponseType]) (рис. 2).
Подробно создание схемы объекта рассмотрено в статье "Создание схемы объекта".
В схему [Отклики по рассылке] ([TestSmsResponseType]) добавьте колонку со следующими свойствами:
- [Заголовок] ([Title]) — "Название" ("Name");
- [Название] ([Name]) — "Name";
- [Тип данных] ([Data type]) — "Строка 50 символов" ("Text (50 characters)").
В схему [Получатель СМС] ([TestSmsTarget]) добавьте колонки со следующими свойствами:
Табл. 1. — Основные свойства колонок схемы объекта [Получатель СМС] ([TestSmsTarget])
[Заголовок] ([Title]) | [Название] ([Name]) | [Тип данных] ([Data type]) |
---|---|---|
Phone number | PhoneNumber | "Строка 50 символов" ("Text (50 characters)") |
SMS text | SmsText | "Строка 50 символов" ("Text (50 characters)") |
Contact | Contact | "Справочник" ("Lookup") — "Contact" |
Test SMS response | TestSmsResponse | "Справочник" ("Lookup") — "TestSmsResponseType" |
Как добавить колонку в схему объекта описано в статье "Создание схемы объекта".
Рис. 1. — Свойства и колонки схемы объекта [Получатель СМС] ([TestSmsTarget])
Рис. 2. — Свойства и колонки схемы объекта [Отклики по рассылке] ([TestSmsResponseType])
Сохраните и опубликуйте объекты.
Cоздайте справочник для объекта TestSmsResponseType и наполните его значениями "Доставлено", "Отменено", "Ошибка доставки" (“Sms delivered”, “Canceled”, “Error while receiving”) и проч. (рис. 3). Идентификаторы (Id) откликов будут использованы в коде страницы редактирования свойств условного перехода из СМС-рассылки.
Рис. 3. — Справочник [Test sms response]
2. Создание новой схемы для элемента [Переход]
Для отображения элемента в пользовательском интерфейсе [Дизайнера кампании] необходимо в пакете разработки создать новую схему модуля. Как создать схему модуля подробно описывается в статье "Создание клиентской схемы". Для созданной схемы нужно установить следующие свойства (рис. 4):
- [Заголовок] ([Title]) — "ProcessTestSmsConditionalTransitionSchema".
- [Название] ([Name]) — "ProcessTestSmsConditionalTransitionSchema".
ВАЖНО
Название схемы для стрелки должно начинаться с префикса “Process”
Рис. 4. — Свойства схемы модуля ProcessTestSmsConditionalTransitionSchema
В секцию [Исходный код] ([Source code]) схемы добавьте следующий исходный код:
define("ProcessTestSmsConditionalTransitionSchema", ["CampaignEnums", "ProcessTestSmsConditionalTransitionSchemaResources", "ProcessCampaignConditionalSequenceFlowSchema"], function(CampaignEnums) { Ext.define("Terrasoft.manager.ProcessTestSmsConditionalTransitionSchema", { extend: "Terrasoft.ProcessCampaignConditionalSequenceFlowSchema", alternateClassName: "Terrasoft.ProcessTestSmsConditionalTransitionSchema", managerItemUId: "4b5e70b0-a631-458e-ab22-856ddc913444", mixins: { parametrizedProcessSchemaElement: "Terrasoft.ParametrizedProcessSchemaElement" }, // Полное имя типа связанного элемента стрелки. typeName: "Terrasoft.Configuration.TestSmsConditionalTransitionElement, Terrasoft.Configuration", // Имя элемента стрелки для привязки к элементам кампании. connectionUserHandleName: "TestSmsConditionalTransition", // Имя карточки редактирования свойств стрелки. editPageSchemaName: "TestSmsConditionalTransitionPropertiesPage", elementType: CampaignEnums.CampaignSchemaElementTypes.CONDITIONAL_TRANSITION, // Коллекция откликов по СМС-рассылке. testSmsResponseId: null, // Признак, который учитывает условие откликов при переводе контактов. isResponseBasedStart: false, getSerializableProperties: function() { var baseSerializableProperties = this.callParent(arguments); // Свойства для сериализации и передачи в серверную часть при сохранении. Ext.Array.push(baseSerializableProperties, ["testSmsResponseId", "isResponseBasedStart"]); return baseSerializableProperties; } }); return Terrasoft.ProcessTestSmsConditionalTransitionSchema; });
Сохраните созданную схему.
3. Создание страницы редактирования свойств элемента [Переход]
Для отображения и изменения свойств элемента кампании необходимо в пакете разработки создать его страницу редактирования. Для этого нужно создать схему, расширяющую CampaignConditionalSequenceFlowPropertiesPage (пакет CampaignDesigner). Как создать замещающую схему подробно описано в статье "Создание клиентской схемы".
Для созданной схемы требуется установить следующие свойства (рис. 5):
- [Заголовок] ([Title]) — "TestSmsConditionalTransitionPropertiesPage".
- [Название] ([Name]) — "TestSmsConditionalTransitionPropertiesPage".
- [Родительский объект] ([Parent object]) — "CampaignConditionalSequenceFlowPropertiesPage".
Рис. 5. — Свойства схемы страницы редактирования TestSmsConditionalTransitionPropertiesPage
В созданную схему добавьте локализуемые строки, основные свойства которых приведены в таблице 2.
Табл. 2. — Основные свойства локализуемых строк
[Название] ([Name]) | [Значение] ([Value]) |
---|---|
ReactionModeCaption | Результат {0} шага? (What is the result of the {0} step?) |
ReactionModeDefault | Передача участников независимо от их ответа (Transfer participants regardless of their response) |
ReactionModeWithCondition | Настройка ответов для передачи участников (Set up responses for transferring participants) |
IsTestSmsDelivered | Тестовое сообщение доставлено (Test SMS delivered) |
IsErrorWhileReceiving | Ошибка при получении (Error while receiving) |
В секцию [Исходный код] ([Source code]) схемы добавьте следующий исходный код:
define("TestSmsConditionalTransitionPropertiesPage", ["BusinessRuleModule"], function(BusinessRuleModule) { return { messages: {}, attributes: { "ReactionModeEnum": { dataValueType: this.Terrasoft.DataValueType.CUSTOM_OBJECT, type: this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN, value: { Default: { value: "0", captionName: "Resources.Strings.ReactionModeDefault" }, WithCondition: { value: "1", captionName: "Resources.Strings.ReactionModeWithCondition" } } }, "ReactionMode": { "dataValueType": this.Terrasoft.DataValueType.LOOKUP, "type": this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN, "isRequired": true }, "IsTestSmsDelivered": { "dataValueType": this.Terrasoft.DataValueType.BOOLEAN, "type": this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN }, "IsErrorWhileReceiving": { "dataValueType": this.Terrasoft.DataValueType.BOOLEAN, "type": this.Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN } }, rules: { "ReactionConditionDecision": { "BindReactionConditionDecisionRequiredToReactionMode": { "ruleType": BusinessRuleModule.enums.RuleType.BINDPARAMETER, "property": BusinessRuleModule.enums.Property.REQUIRED, "conditions": [{ "leftExpression": { "type": BusinessRuleModule.enums.ValueType.ATTRIBUTE, "attribute": "ReactionMode" }, "comparisonType": this.Terrasoft.ComparisonType.EQUAL, "rightExpression": { "type": BusinessRuleModule.enums.ValueType.CONSTANT, "value": "1" } }] } } }, methods: { // Формирует соответствие смс-откликов (на основании справочника TestSmsResponseType). // Предполагается, что в системе есть справочник TestSmsResponseType // с записями TestSmsDelivered и ErrorWhileReceiving. getResponseConfig: function() { return { "IsTestSmsDelivered": "F2FC75B3-58C3-49A6-B2F2-353262068145", "IsErrorWhileReceiving": "37B9F9D5-E897-4B7B-A65E-3B3799A18D72" }; }, subscribeEvents: function() { this.callParent(arguments); // Привязка обработчика к событию изменения значения атрибута ReactionMode this.on("change:ReactionMode", this.onReactionModeLookupChanged, this); }, // Метод-обработчик события изменения атрибута ReactionMode. onReactionModeLookupChanged: function() { var reactionModeEnum = this.get("ReactionModeEnum"); var reactionMode = this.get("ReactionMode"); var decisionModeEnabled = (reactionMode && reactionMode.value === reactionModeEnum.WithCondition.value); if (!decisionModeEnabled) { this.set("ReactionConditionDecision", null); } }, // Инициализирует свойства viewModel для отображения карточки при открытии. initParameters: function(element) { this.callParent(arguments); var isResponseBasedStart = element.isResponseBasedStart; this.initReactionMode(isResponseBasedStart); this.initTestSmsResponses(element.testSmsResponseId); }, // Вспомогательный метод, обрезающий строку до указанной длины и добавляющий троеточие в конце. cutString: function(strValue, strLength) { var ellipsis = Ext.String.ellipsis(strValue.substring(strLength), 0); return strValue.substring(0, strLength) + ellipsis; }, // Устанавливает значение статуса "СМС доставлено". initIsTestSmsDelivered: function(value) { if (value === undefined) { value = this.get("IsTestSmsDelivered"); } this.set("IsTestSmsDelivered", value); }, // Уустанавливает значение статуса "Ошибка при получении". initIsErrorWhileReceiving: function(value) { if (value === undefined) { var isErrorWhileReceiving = this.get("IsErrorWhileReceiving"); value = isErrorWhileReceiving; } this.set("IsErrorWhileReceiving", value); }, // Инициализирует выбранные отклики при открытии карточки. initTestSmsResponses: function(responseIdsJson) { if (!responseIdsJson) { return; } var responseIds = JSON.parse(responseIdsJson); var config = this.getResponseConfig(); Terrasoft.each(config, function(propValue, propName) { if (responseIds.indexOf(propValue) > -1) { this.set(propName, true); } }, this); }, initReactionMode: function(value) { var isDefault = !value; this.setLookupValue(isDefault, "ReactionMode", "WithCondition", this); }, // Вспомогательный метод, извлекающий массив идентификаторов из входящего JSON параметра. getIds: function(idsJson) { if (idsJson) { try { var ids = JSON.parse(idsJson); if (this.Ext.isArray(ids)) { return ids; } } catch (error) { return []; } } return []; }, onPrepareReactionModeList: function(filter, list) { this.prepareList("ReactionModeEnum", list, this); }, // Сохраняет значения откликов и настройку, добавлять условия откликов или нет. saveValues: function() { this.callParent(arguments); var element = this.get("ProcessElement"); var isResponseBasedStart = this.getIsReactionModeWithConditions(); element.isResponseBasedStart = isResponseBasedStart; element.testSmsResponseId = this.getTestSmsResponseId(isResponseBasedStart); }, // Получает сериализированые id выбранных откликов. getTestSmsResponseId: function(isResponseActive) { var responseIds = []; if (isResponseActive) { var config = this.getResponseConfig(); Terrasoft.each(config, function(propValue, propName) { var attrValue = this.get(propName); if (attrValue && propValue) { responseIds.push(propValue); } }, this); } return JSON.stringify(responseIds); }, getLookupValue: function(parameterName) { var value = this.get(parameterName); return value ? value.value : null; }, getContextHelpCode: function() { return "CampaignConditionalSequenceFlow"; }, getIsReactionModeWithConditions: function() { return this.isLookupValueEqual("ReactionMode", "1", this); }, getSourceElement: function() { var flowElement = this.get("ProcessElement"); if (flowElement) { return flowElement.findSourceElement(); } return null; }, // Подставляет в текст название элемента, из которого выходит стрелка. getQuestionCaption: function() { var caption = this.get("Resources.Strings.ReactionModeCaption"); caption = this.Ext.String.format(caption, this.getSourceElement().getCaption()); return caption; } }, diff: /**SCHEMA_DIFF*/[ // Контейнер. { "operation": "insert", "name": "ReactionContainer", "propertyName": "items", "parentName": "ContentContainer", "className": "Terrasoft.GridLayoutEdit", "values": { "layout": { "column": 0, "row": 2, "colSpan": 24 }, "itemType": this.Terrasoft.ViewItemType.GRID_LAYOUT, "items": [] } }, // Заголовок. { "operation": "insert", "name": "ReactionModeLabel", "parentName": "ReactionContainer", "propertyName": "items", "values": { "layout": { "column": 0, "row": 0, "colSpan": 24 }, "itemType": this.Terrasoft.ViewItemType.LABEL, "caption": { "bindTo": "getQuestionCaption" }, "classes": { "labelClass": ["t-title-label-proc"] } } }, // Список. { "operation": "insert", "name": "ReactionMode", "parentName": "ReactionContainer", "propertyName": "items", "values": { "contentType": this.Terrasoft.ContentType.ENUM, "controlConfig": { "prepareList": { "bindTo": "onPrepareReactionModeList" } }, "isRequired": true, "layout": { "column": 0, "row": 1, "colSpan": 24 }, "labelConfig": { "visible": false }, "wrapClass": ["no-caption-control"] } }, // Элемент списка. { "operation": "insert", "parentName": "ReactionContainer", "propertyName": "items", "name": "IsTestSmsDelivered", "values": { "wrapClass": ["t-checkbox-control"], "visible": { "bindTo": "ReactionMode", "bindConfig": { converter: "getIsReactionModeWithConditions" } }, "caption": { "bindTo": "Resources.Strings.IsTestSmsDelivered" }, "layout": { "column": 0, "row": 2, "colSpan": 22 } } }, // Элемент списка. { "operation": "insert", "parentName": "ReactionContainer", "propertyName": "items", "name": "IsErrorWhileReceiving", "values": { "wrapClass": ["t-checkbox-control"], "visible": { "bindTo": "ReactionMode", "bindConfig": { converter: "getIsReactionModeWithConditions" } }, "caption": { "bindTo": "Resources.Strings.IsErrorWhileReceiving" }, "layout": { "column": 0, "row": 3, "colSpan": 22 } } } ]/**SCHEMA_DIFF*/ }; } );
Сохраните созданную схему.
4. Создание серверной части элемента [Переход] из элемента [СМС-рассылка]
Чтобы реализовать возможность сохранения базовых и пользовательских свойств элемента кампании, для него необходимо создать класс, взаимодействующий с серверной частью приложения. Класс должен быть наследником CampaignSchemaElement и переопределять методы ApplyMetaDataValue() и WriteMetaData().
Для этого создайте схему исходного кода со следующими свойствами:
- [Заголовок] ([Title]) — "TestSmsConditionalTransitionElement".
- [Название] ([Name]) — "TestSmsConditionalTransitionElement".
Как создать схему исходного кода подробно описано в статье "Создание схемы [Исходный код]".
В секцию [Исходный код] ([Source code]) схемы необходимо добавить следующий исходный код:
namespace Terrasoft.Configuration { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using Newtonsoft.Json; using Terrasoft.Common; using Terrasoft.Core; using Terrasoft.Core.Campaign; using Terrasoft.Core.DB; using Terrasoft.Core.Process; [DesignModeProperty(Name = "TestSmsResponseId", UsageType = DesignModeUsageType.NotVisible, MetaPropertyName = TestSmsResponseIdPropertyName)] [DesignModeProperty(Name = "IsResponseBasedStart", UsageType = DesignModeUsageType.Advanced, MetaPropertyName = IsResponseBasedStartPropertyName)] public class TestSmsConditionalTransitionElement : ConditionalSequenceFlowElement { private const string TestSmsResponseIdPropertyName = "TestSmsResponseId"; private const string IsResponseBasedStartPropertyName = "IsResponseBasedStart"; public TestSmsConditionalTransitionElement() {} public TestSmsConditionalTransitionElement(TestSmsConditionalTransitionElement source) : this(source, null, null) {} public TestSmsConditionalTransitionElement(TestSmsConditionalTransitionElement source, Dictionary<Guid, Guid> dictToRebind, Core.Campaign.CampaignSchema parentSchema) : base(source, dictToRebind, parentSchema) { IsResponseBasedStart = source.IsResponseBasedStart; _testSmsResponseIdJson = JsonConvert.SerializeObject(source.TestSmsResponseId); } private string _testSmsResponseIdJson; private IEnumerable<Guid> Responses { get { return TestSmsResponseId; } } [MetaTypeProperty("{DC597899-B831-458A-A58E-FB43B1E266AC}")] public IEnumerable<Guid> TestSmsResponseId { get { return !string.IsNullOrWhiteSpace(_testSmsResponseIdJson) ? JsonConvert.DeserializeObject<IEnumerable<Guid>>(_testSmsResponseIdJson) : Enumerable.Empty<Guid>(); } } [MetaTypeProperty("{3FFA4EA0-62CC-49A8-91FF-4096AEC561F6}", IsExtraProperty = true, IsUserProperty = true)] public virtual bool IsResponseBasedStart { get; set; } protected override void ApplyMetaDataValue(DataReader reader) { base.ApplyMetaDataValue(reader); switch (reader.CurrentName) { case TestSmsResponseIdPropertyName: _testSmsResponseIdJson = reader.GetValue<string>(); break; case IsResponseBasedStartPropertyName: IsResponseBasedStart = reader.GetBoolValue(); break; default: break; } } public override void WriteMetaData(DataWriter writer) { base.WriteMetaData(writer); writer.WriteValue(IsResponseBasedStartPropertyName, IsResponseBasedStart, false); writer.WriteValue(TestSmsResponseIdPropertyName, _testSmsResponseIdJson, null); } public override object Clone() { return new TestSmsConditionalTransitionElement(this); } public override object Copy(Dictionary<Guid, Guid> dictToRebind, Core.Campaign.CampaignSchema parentSchema) { return new TestSmsConditionalTransitionElement(this, dictToRebind, parentSchema); } // Переопределяет фабричный метод по созданию исполняемого элемента // Возвращает элемент с типом TestSmsConditionalTransitionFlowElement public override ProcessFlowElement CreateProcessFlowElement(UserConnection userConnection) { var sourceElement = SourceRef as TestSmsElement; var executableElement = new TestSmsConditionalTransitionFlowElement { UserConnection = userConnection, TestSmsResponses = TestSmsResponseId, PhoneNumber = sourceElement.PhoneNumber, SmsText = sourceElement.SmsText }; InitializeCampaignProcessFlowElement(executableElement); InitializeCampaignTransitionFlowElement(executableElement); InitializeConditionalTransitionFlowElement(executableElement); return executableElement; } } }
Сохраните и опубликуйте созданную схему.
5. Создание исполняемого элемента для перехода из элемента [СМС-рассылка]
Чтобы добавить функциональность, которая выполнит учет условия откликов по отправленой смс-рассылке, создайте исполняемый элемент. Это класс, наследник класса ConditionalTransitionFlowElement.
Для создания исполняемого элемента в пакете разработки создайте схему исходного кода со следующими свойствами:
- [Заголовок] ([Title]) — "TestSmsConditionalTransitionFlowElement".
- [Название] ([Name]) — "TestSmsConditionalTransitionFlowElement".
В секцию [Исходный код] ([Source code]) схемы необходимо добавить следующий исходный код:
namespace Terrasoft.Configuration { using System; using System.Collections.Generic; using System.Linq; using Terrasoft.Common; using Terrasoft.Core.DB; public class TestSmsConditionalTransitionFlowElement : ConditionalTransitionFlowElement { public string SmsText { get; set; } public string PhoneNumber { get; set; } public IEnumerable<Guid> TestSmsResponses { get; set; } private void ExtendWithResponses() { TransitionQuery.CheckArgumentNull("TransitionQuery"); if (TestSmsResponses.Any()) { Query responseSelect = GetSelectByParticipantResponses(); TransitionQuery.And("ContactId").In(responseSelect); } } private Query GetSelectByParticipantResponses() { var responseSelect = new Select(UserConnection) .Column("ContactId") .From("TestSmsTarget") .Where("SmsText").IsEqual(Column.Parameter(SmsText)) .And("PhoneNumber").IsEqual(Column.Parameter(PhoneNumber)) .And("TestSmsResponseId") .In(Column.Parameters(TestSmsResponses)) as Select; responseSelect.SpecifyNoLockHints(true); return responseSelect; } protected override void CreateQuery() { base.CreateQuery(); ExtendWithResponses(); } } }
Сохраните и опубликуйте созданную схему.
6. Создание замещающего модуля CampaignConnectorManager для добавления логики работы перехода
Для добавления специфической логики работы перехода при изменении источника (элемента, из которого выходит стрелка) в пакете разработки создайте новую схему модуля замещающего модуль CampaignConnectorManager. Как создать схему модуля подробно описывается в статье "Создание клиентской схемы". Для созданной схемы нужно установить следующие свойства:
- [Заголовок] ([Title]) — "TestSmsCampaignConnectorManager".
- [Название] ([Name]) — "TestSmsCampaignConnectorManager".
В секцию [Исходный код] ([Source code]) схемы необходимо добавить следующий исходный код:
define("TestSmsCampaignConnectorManager", [], function() { Ext.define("Terrasoft.TestSmsCampaignConnectorManager", { // Указываем, что замещаем модуль CampaignConnectorManager override: "Terrasoft.CampaignConnectorManager", // Добавляем маппинг название схема-элемента источника для стрелки - тип стрелки (full name) initMappingCollection: function() { this.callParent(arguments); this.connectorTypesMappingCollection.addIfNotExists("TestSmsElementSchema", "Terrasoft.ProcessTestSmsConditionalTransitionSchema"); }, // Виртуальный метод для перегрузки // Логика для процессинга стрелки перед ее подменой стрелкой с новым типом. additionalBeforeChange: function(prevTransition, sourceItem, targetItem) { // additional logic here }, // Виртуальный метод для перегрузки // Заполнение специфических полей созданной стрелки на основе предыдущей стрелки. fillAdditionalProperties: function(prevElement, newElement) { if (newElement.getTypeInfo().typeName === "ProcessTestSmsConditionalTransitionSchema") { // Переносим настроенные отклики, если предыдущая стрелка такого же типа newElement.testSmsResponseId = prevElement.testSmsResponseId ? prevElement.testSmsResponseId : null; // Так же переносим и настройку учета откликов newElement.isResponseBasedStart = prevElement.isResponseBasedStart ? prevElement.isResponseBasedStart : false; } } }); });
Сохраните созданную схему.
7. Подключение замещающего модуля CampaignConnectorManager
Для подключения модуля, созданного на предыдущем шаге создайте замещающий клиентский модуль, в котором в качестве родительского объекта укажите BootstrapModulesV2 из пакета NUI. Процесс создания замещающего клиентского модуля описан в статье "Создание клиентской схемы".
В секцию [Исходный код] ([Source code]) схемы необходимо добавить следующий исходный код:
// Ставим в зависимости созданный ранее модуль TestSmsCampaignConnectorManager define("BootstrapModulesV2", ["TestSmsCampaignConnectorManager"], function() {});
Сохраните созданную схему.
К СВЕДЕНИЮ
В реальном примере желательно создать отдельную схему объекта [СМС-рассылка]. Объекты [TestSmsElement] и [TestSmsConditionalTransitionElement] будут содержать [Id] этого объекта, а не поля SmsText, PhoneNumber… Исполняемый элемент TestSmsCampaignProcessElement в методе Execute() должен содержать логику по добавлению контактов в аудиторию рассылки. Отдельный механизм (или несколько механизмов) должны выполнить отправку рассылки, а затем зафиксировать отклики участников. На основании этих откликов и будет выполнена работа стрелки по перемещению аудитории кампании на следующий шаг.