Введение в написание сценариев

Примечание
Перед началом работы со сценариями рекомендуется ознакомиться со стандартами написания программного кода на языке C#.

В данной статье вам будет представлен обучающий материал по написанию сценариев в системе ELMA 3. Предполагается, что вы уже знакомы с самой системой ELMA и отвечаете требованиям к разработчику сценариев.

ELMA API - инструмент для разработки сценариев
С версии 3.2.9.20379 в системе доступен ELMA API. Подробнее...

Начало работы. Ваш первый сценарий.

Перейдем сразу к делу. Для успешного обучения лучший метод – это практика.

Для написания сценариев в курсе обучения можно использовать Бесплатную Демо-версию системы.

Работа со сценариями ведется в Дизайнере. Сами сценарии используются во многих местах системы, наиболее же распространенным является использование их в блоке Сценарий бизнес-процессов.

Создадим наш первый сценарий. Для этого создайте бизнес-процесс (назовем его "Калькулятор") и добавьте туда следующие данные:

  • динамическая зона ответственности (так, чтобы все пользователи могли запускать процесс);
  • контекстные переменные:
    • Поле ввода – дробное число, не пустое, по умолчанию = 0;
    • Текущий результат – дробное число, не пустое, по умолчанию = 0;
  • на диаграмму процесса положите одну пользовательскую задачу и один блок Сценарий. В пользовательской задаче выведите контекстные переменные:
    • Поле ввода – для редактирования;
    • Текущий результат – для чтения.

После этого можно приступать к написанию самого сценария. Для этого откройте настройки блока Сценарий и добавьте новый метод – Calculate (рекомендуется методам в сценариях давать английские названия, но использование русских букв не запрещено). Текст сценария пока не заполняйте, нажмите кнопку Ок и потом на той же вкладке в настройках кнопку Перейти. При этом откроется другая вкладка в процессе – Сценарии. На этой вкладке расположены все сценарии процесса. На данной вкладке используется компонент редактора SharpDevelop, он позволяет использовать автодополнение кода, что существенно упрощает разработку.

Для каждого процесса создается отдельный класс, наследующий базовый класс EleWise.ELMA.Workflow.Scripts.ProcessScriptBase<Context> (где Context – это класс контекста процесса, вы можете увидеть строку using Context = EleWise.ELMA.Model.Entities.ProcessContext.P_Kaljkulyator;). В этом классе методы представляют собой отдельные сценарии. В нашем случае создается пустой метод Calculate в который передается контекст процесса context:

C# Code
public void Calculate(Context context)
{

}

Для начала давайте просто сложим наше текущее значение и то, что пользователь ввел в поле ввода:

C# Code
public void Calculate(Context context)
{
    context.TekuschiyRezuljtat = context.TekuschiyRezuljtat + context.PoleVvoda;
}

Как видите, все достаточно просто. Здесь мы присваиваем значению одной переменной сумму ее текущего значения и значения переменной Поле ввода. Теперь, когда пользователь запустит процесс и в задаче укажет в поле ввода любое число, после перехода по кнопке Вычисления в контексте процесса изменится значение переменной Текущий результат. Так можно повторять сколько угодно раз.

Таким образом, вы видите, что любые изменения, внесенные в контекст в блоке Сценарий сохраняются автоматически.

Но это, конечно, очень простой пример. Он показывает самые базовые операции, и цель его в том, чтобы познакомить пользователя с принципом работы со сценариями.

Второй пример сценария. Работа с объектами

Наиболее интересными, конечно, являются возможности по работе с объектами системы (иногда их называют сущности). Создадим в Дизайнере свой собственный объект Питомец (будем вести учет домашних питомцев наших клиентов) в группе Работа с клиентами. Добавьте в объект следующие свойства:

  • Имя – тип Строка, обязательно для заполнения, является наименованием, отображать в таблице;
  • Вид – тип Выпадающий список: Кошка, Собака, Попугай, Рыбка, Черепаха, Змея;
  • Возраст – тип Целое число, может иметь пустое значение;
  • Хозяин – ссылка на объект Контакт, обязательно для заполнения, отображать в таблице.

После этого сохраните и опубликуйте модель данных. Не забудьте перезапустить сервер на вкладке Публикация.

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

Далее создадим процесс, который будет создавать питомца для выбранного контакта, при этом предварительно проверяя, что у контакта еще нет питомца с таким именем. Как видите, тут требуется решить две задачи:

  1. Создание записи объекта в сценарии.
  2. Проверка на наличие записи по определенному условию.

Создание записи объекта

Для выполнения первой части нам необходимо снова создать процесс "Новый питомец". В этом процессе создадим контекстные переменные:

  • Контакт – ссылка на объект Контакт, является входной;
  • Имя питомца – тип Строка;
  • Вид питомца – тип Выпадающий список: Кошка, Собака, Попугай, Рыбка, Черепаха, Змея;
  • Возраст питомца – тип Целое число;
  • Новый питомец – ссылка на объект Питомец;
  • Ошибка заполнения – тип Строка (нужна для вывода ошибки, если не выполнено какое-то условие).

После этого на диаграмме процесса добавим 2 пользовательские задачи и один сценарий.

В задаче "Ввести данные о питомце" добавим контекст Контакт, Имя питомца, Вид питомца, Возраст питомца для редактирования. В задаче "Проверить" выведем все переменные контекста только для чтения. Обратите внимание, что поле Имя питомца не нужно делать обязательным для заполнения, так как мы проверим его в скрипте (однако при настройке реального приложения нужно проверять обязательность заполнения этого поля еще и на форме).

Теперь перейдем к редактированию сценария. Создайте новый метод CreateNewPet и вставьте туда следуюший код:

C# Code
public void CreateNewPet(Context context)
{
    //Проверяем, что переменная Контакт выбрана
    if(context.Kontakt == null)
    {
        context.OshibkaZapolneniya = "Не указан Контакт";
        //Выход из скрипта
        return;
    }
    
    //Проверяем, что Имя питомца не пустое
    if(String.IsNullOrWhiteSpace(context.ImyaPitomca))
    {
        context.OshibkaZapolneniya = "Не указано Имя питомца";
        //Выход из скрипта
        return;
    }
    
    //Создаем новую запись для объекта Питомец
    var newPet = EntityManager<Pitomec>.Create();
    
    //Присваиваем значения из контекста
    newPet.Imya = context.ImyaPitomca;
    newPet.Vid = context.VidPitomca;
    newPet.Vozrast = context.VozrastPitomca;
    newPet.Hozyain = context.Kontakt;
    
    //Сохраняем новую запись в БД
    newPet.Save();
    
    //Присваиваем новую запись в контекст
    context.NovyyPitomec = newPet;
}

Как видите, все достаточно понятно из комментариев. Единственный момент, который нужно прояснить, – это использование класса EntityManager<Pitomec>.

Менеджер сущности 

Вообще есть несколько типов менеджеров сущностей про них вам лучше почитать в статье описывающей менеджеры более подробно.

В нашем случае мы использовали статический класс для доступа к базовому типизированному менеджеру сущности. Сам класс EntityManager<T> – типизированный и для доступа к конкретному менеджеру мы должны указать тип объекта (в нашем случае это как раз Питомец, имя класса у него Pitomec). После этого можно обратиться к статическому полю Instance, которое вернет объект менеджера сущности, или вызвать статический метод Create, который создаст новый объект указанного типа (в нашем случае Питомец) без записи в базу.

Далее идет присвоение значений свойств нового объекта из контекста процесса и сохранение объекта в БД при помощи метода Save

Внимание
При создании новой сущности всегда вызывайте Save для сохранения в базу. Но помните, сохранение происходит не сразу а только по завершению всего сценария, из этого следует, что если вы попытаетесь сохранить новую запись и сделать выборку по условию то эта запись в выборку не попадет.
Всегда проверяйте, что обязательные поля объекта заполнены перед сохранением!

Попробуйте теперь опубликовать процесс, у вас должна появиться ошибка: 

Тип "EleWise.ELMA.CRM.Models.IContact" определен в сборке, ссылка на которую отсутствует. Следует добавить ссылку на сборку "EleWise.ELMA.CRM, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null".

Давайте теперь добавим ссылку на сборку. Для этого необходимо в окне редактирования сценария справа в панели Настройки нажать кнопку Добавить - Добавить ссылку на сборку.

Далее в окне можно отфильтровать по имени сборки, например указать CRM в поле фильтра и после этого выбрать нужную сборку (в нашем случае это EleWise.ELMA.CRM, как указано в тексте ошибки)

Ссылки на сборки
Вы можете добавлять ссылки на сборки системы ELMA или на глобальные сборки .NET зарегистрированные в GAC.

Если вы все сделали правильно, то теперь вы можете запускать процесс (даже из карточки контакта, так как переменную Контакт мы пометили как входную) и создавать новые записи для питомцев. При этом, если какие-то из условий не будут выполнены, то ошибка будет записана и выведена пользователю в переменную.  

Вообще вы можете придумать и свой механизм обработки ошибок сценария, но это выходит за рамки данной статьи

Поиск записи по условию

Далее мы должны рассмотреть возможность поиска записи по условию и обработки результатов. Это нужно нам для проверки, что у контакта уже есть питомец с таким именем. Давайте немного обновим сценарий (добавленные строки выделены): 

C# Code
public void CreateNewPet(Context context)
{
    //Проверяем, что переменная Контакт выбрана
    if(context.Kontakt == null)
    {
        context.OshibkaZapolneniya = "Не указан Контакт";
        //Выход из скрипта
        return;
    }
    
    //Проверяем, что Имя питомца не пустое
    if(String.IsNullOrWhiteSpace(context.ImyaPitomca))
    {
        context.OshibkaZapolneniya = "Не указано Имя питомца";
        //Выход из скрипта
        return;
    }
    
    //Получаем список питомцев для указанного контакта и для указанного имени (полное совпадение)
    var pets = EntityManager<Pitomec>.Instance.Find(p => p.Hozyain == context.Kontakt && p.Imya == context.ImyaPitomca);
    
    //Проверяем, есть ли в коллекции хоть одна запись
    if(pets.Any())
    {
        context.OshibkaZapolneniya = "Уже есть питомец с указанным именем";
        //Выход из скрипта
        return;
    }
    

    //Создаем новую запись для объекта Питомец
    var newPet = EntityManager<Pitomec>.Create();
    
    //Присваиваем значения из контекста
    newPet.Imya = context.ImyaPitomca;
    newPet.Vid = context.VidPitomca;
    newPet.Vozrast = context.VozrastPitomca;
    newPet.Hozyain = context.Kontakt;
    
    //Сохраняем новую запись в БД
    newPet.Save();
    
    //Присваиваем новую запись в контекст
    context.NovyyPitomec = newPet;
}

Как видите, здесь мы снова обращаемся к менеджеру сущности, но на этот раз используем метод Find для поиска по выражению. 

Больше информации о возможности поиска сущностей вы можете найти в отдельной статье.

В нашем случае мы ищем все объекты типа Питомец (Pitomec), где поле Хозяин (Hozyain) совпадает с переменной Контакт и поле Имя (Imya) равно переменной Имя питомца в контексте процесса. Мы можем использовать поиск по выражению, так как у нас используются только условия сравнения. Далее проверяется наличие записи в результирующей коллекции. В нашем случае, если найдется хоть одна запись удовлетворяющая условию, то нужно вывести сообщение об ошибке.

Как видите, мы решили поставленную задачу по добавлению питомца для выбранного контакта при помощи достаточно простого скрипта. Далее мы рассмотрим более сложный пример.

Третий пример сценария. Редактирование и выпадающие списки

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

Чтобы корректно обработать все варианты (наш процесс ведь может быть запущен с уже указанным контактом на входе), нужно добавить еще один сценарий, он должен быть расположен сразу после стартового события, функцию назовем OnStartProcess

C# Code
public void OnStartProcess(Context context)
{
    FillPitomecDDL(context);
}
private void FillPitomecDDL(Context context)
{
    //Получаем настройки выпадающего списка для контекстной переменной Питомец
    var settings = (DropDownListSettings) context.GetSettingsFor(c => c.Pitomec);
    
    //Очищаем все элементы
    settings.Items.Clear();
    
    if(context.Kontakt == null)
    {
        //Если контакт не выбран - дальше ничего не делаем
        return;
    }
    
    //Находим всех питомцев, для выбранного контакта
    var pets = EntityManager<IPitomec>.Instance.Find(p => p.Hozyain == context.Kontakt);
    
    foreach (var pet in pets)
    {
        //Добавляем питомца в выпадающий список
        settings.Items.Add(new DropDownItem(pet.Id.ToString(), pet.Imya));
    }
    
    //Сохраняем настройки выпадающего списка
    settings.Save();

     //Обнуляем ранее выбранное значение переменной Питомец
    context.Pitomec = null;
}

Как видите, тут мы используем определение дополнительной функции FillPitomecDDL, в которой производим заполнение выпадающего списка Питомец (эта функция понадобится нам еще раз в дальнейшем для обработки событий на форме). Обратите внимание, что настройки выпадающего списка привязаны к контексту процесса, а значит для каждого экземпляра они будут свои.

Следующим шагом необходимо настроить форму первой задачи в процессе так, чтобы можно было создавать либо редактировать связанных с контактом питомцев на одном шаге. Для этого добавим на форму переменные Контакт, Питомец, Вид питомца и Возраст питомца. Далее настроим для формы скрипт при загрузке (функция OnPitomecCreateFormLoad): 

C# Code
public void OnPitomecCreateFormLoad(Context context, EleWise.ELMA.Model.Views.FormViewBuilder<Context> form)
{
    var pet = GetPitomec(context);
    
    if(pet !null)
    {
        //Устанавливаем признак Только для чтения на форме для свойства Вид питомца
        form.For(c => c.VidPitomca).ReadOnly(true);
    }
}
private Pitomec GetPitomec(Context context)
{
    if(context.Kontakt == null)
    {
        return null;
    }
    
    var pName = context.Pitomec.Value;
    if(string.IsNullOrWhiteSpace(pName))
    {
        return null;
    }
    
    var pet = EntityManager<Pitomec>.Instance.Find(p => p.Hozyain == context.Kontakt && p.Imya == pName).FirstOrDefault();
    
    return pet;
}

Как видите, тут мы просто устанавливаем для переменной Вид питомца на форме признак, что она только для чтения, в зависимости от того выбран ли существующий питомец или он новый. Этот скрипт нужен для того случая, когда на эту задачу будет переход для повторного редактирования по переходу "Ввести данные заново". 

Далее нам нужно настроить поведение переменных на форме. Для этого зайдем в Настройки задачи и добавим для переменных на форме сценарии при изменении. Для переменной Контакт (функция OnContactSelect): 

C# Code
public void OnContactSelect(Context context, EleWise.ELMA.Model.Views.FormViewBuilder<Context> form)
{
    FillPitomecDDL(context);
    
    form.For(c => c.VidPitomca).ReadOnly(false);
    context.VidPitomca = null;
    context.VozrastPitomca = null;
}

и для переменной Питомец (функция OnPitomecSelect): 

C# Code
public void OnPitomecSelect(Context context, EleWise.ELMA.Model.Views.FormViewBuilder<Context> form)
{
    if(context.Kontakt == null)
    {
        return;
    }
    
    //Выбираем питомца
    var pet = GetPitomec(context);
    
    if(pet !null)
    {
        //Если уже сохранен в справочнике, то заполняем поля контекста
        context.ImyaPitomca = pet.Imya;
        context.VozrastPitomca = pet.Vozrast;
        context.VidPitomca = pet.Vid;
        context.NovyyPitomec = (Pitomec) pet;
        
        //Запрещаем редактирование переменной Вид питомца
        form.For(c => c.VidPitomca).ReadOnly(true);
    }
    else
    {
        //Иначе сбрасываем на пустые значения и устанавливаем имя из выпадающего списка
        context.ImyaPitomca = context.Pitomec.Value;
        context.VozrastPitomca = 0;
        context.VidPitomca = null;
        context.NovyyPitomec = null;
        
        //Разрешаем редактирование переменной Вид питомца
        form.For(c => c.VidPitomca).ReadOnly(false);
    }
}

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

Если же ввести в поле Питомец имя, которого еще нет у данного контакта, то поле Вид будет снова доступно для редактирования.

Таким образом, используя настройки выпадающего списка и скрипты на форме вы можете организовывать каскадные списки с подчиненными справочниками.

После всех изменений нам нужно только немного переписать сценарий сохранения питомца: 

C# Code
public void CreateNewPet(Context context)
{
    //Проверяем, что переменная Контакт выбрана
    if(context.Kontakt == null)
    {
        context.OshibkaZapolneniya = "Не указан Контакт";
        //Выход из скрипта
        return;
    }
    
    //Проверяем, что Имя питомца не пустое
    if(String.IsNullOrWhiteSpace(context.ImyaPitomca))
    {
        context.OshibkaZapolneniya = "Не указано Имя питомца";
        //Выход из скрипта
        return;
    }
    
    context.NovyyPitomec = null;
    
    //Получаем питомца для указанного контакта и для указанного имени (полное совпадение)
    var pet = GetPitomec(context);
    
    if(pet !null)
    {
        //Если питомец найден, присваиваем его в контекст
        context.NovyyPitomec = pet;
    }
    
    //Проверяем переменную в контексте
    if(context.NovyyPitomec == null)
    {
        //Создаем новую запись для объекта Питомец
        var newPet = EntityManager<Pitomec>.Create();
        
        //Присваиваем значения из контекста
        newPet.Imya = context.ImyaPitomca;
        newPet.Vid = context.VidPitomca;
        newPet.Vozrast = context.VozrastPitomca;
        newPet.Hozyain = context.Kontakt;
        
        //Сохраняем новую запись в БД
        newPet.Save();
        
        //Присваиваем новую запись в контекст
        context.NovyyPitomec = newPet;
    }
    else
    {
        //Обновляем только возраст питомца
        context.NovyyPitomec.Vozrast = context.VozrastPitomca;
        context.NovyyPitomec.Save();
    }
}

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

Что почитать дальше

Изучив эти три примера, вы уже сможете самостоятельно реализовать очень много сценариев в системе. Комбинируя сценарии на форме и сценарии между задачами, можно обрабатывать самые сложные бизнес-задачи.

Для более глубокого изучения работы со сценариями и разбора примеров рекомендуем ознакомиться со статьями: