# 🍬 IEE BPM | Расширение базовой платформы

Модуль расширяет базовый функционал платформы ELMA, включающее дополнительные библиотеки, функции и методы:  
• Режим тестирования/обучения (вход под любым пользователем при наличии соответствующих прав)  
• Собственный логотип и название системы на странице авторизации  
• Баннер работы в тестовом режиме  
• Оповещение пользователей об профилактических работах  
• Автосохранение проектных задач  
• Перезапуск сервера по расписанию (устранение утечек памяти)  
• Много разных хелперов  
• Счетчики для объектов системы (аналог нумераторов в документах)  
• Входящая почта для пользователей  
• Кворум сервисы  
• Возможность редактирования комментариев автором

# Настройки

Администрирование компонента и тонкие настройки системы

# Автоматическое удаление логов системы

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

1\. Выбрать меню **Администрирование** - **Тюнинг системы**.

[![image-1625481256235.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-07/scaled-1680-/JwD6UJLVDJvrsUtz-image-1625481256235.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-07/JwD6UJLVDJvrsUtz-image-1625481256235.png)

2\. Установить Х дней, старше которых логи системы будут удалены, либо 0 для отключения автоматической очистки.

[![image-1625481374337.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-07/scaled-1680-/9RBIh2YpiveTSAei-image-1625481374337.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-07/9RBIh2YpiveTSAei-image-1625481374337.png)

Если было установлено количество дней больше 0, то в планировщике появится задание **Удаление старых логов системы**, которое ежедневно будет проверять старые логи и при необходимости удалять их:

[![image-1625481558939.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-07/scaled-1680-/x4xNHz54AxsPf1KE-image-1625481558939.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-07/x4xNHz54AxsPf1KE-image-1625481558939.png)

В результате успешной очистки будет сформирован лог обработки:

[![image-1625481641118.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-07/scaled-1680-/fnbQzDjjYPk6mnvj-image-1625481641118.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-07/fnbQzDjjYPk6mnvj-image-1625481641118.png)

# Хелперы

namespace ITino.ELMA.Common.Helpers

# ModelHelper

```C#
/// <summary>
/// Прописано ли свойство в конфигурации
/// </summary>
/// <param name="pm">Метаданные свойства</param>
/// <returns></returns>
public static bool IsConfigProperty(PropertyMetadata pm)
```

```C#
/// <summary>
/// Получить Uid значения перечисления
/// </summary>
/// <param name="value">Значение перечисления</param>
/// <returns></returns>
public static Guid GetEnumUid(object value)
```

```C#
/// <summary>
/// Получить отображаемое описание значения перечисления
/// </summary>
/// <param name="value">Значение перечисления</param>
/// <returns></returns>
public static string GetEnumDescription(object value)
```

```C#
/// <summary>
/// Название типа сущности
/// </summary>
/// <param name="entity">Сущность</param>
/// <returns></returns>
public static string GetEntityDisplayName(IEntity entity)
```

```C#
/// <summary>
/// Описание типа сущности
/// </summary>
/// <param name="entity">Сущность</param>
/// <returns></returns>
public static string GetEntityDescription(IEntity entity)
```

```C#
/// <summary>
/// Проверить наличие элемента справочника и создать при необходимости
/// </summary>
/// <param name="uid">Uid элемента справочника</param>
/// <param name="values">Значения свойств</param>
/// <typeparam name="T">Тип сущности</typeparam>
public static void CheckEntityPresent<T>(Guid uid, object values) where T : IEntity
```

```C#
/// <summary>
/// Удалить элемент справочника (если есть)
/// </summary>
/// <param name="uid">Uid элемента справочника</param>
/// <typeparam name="T">Тип сущности</typeparam>
public static void RemoveEntityIfPresent<T>(Guid uid) where T : IEntity
```

<p class="callout info">ELMA4</p>

```C#
 /// <summary>
/// Обрезать часть даты и времени
/// </summary>
/// <param name="dateTime"></param>
/// <param name="timeSpan"></param>
/// <returns></returns>
public static DateTime Truncate(this DateTime dateTime, TimeSpan timeSpan)
```

```C#
/// <summary>
/// Обрезать миллисекунды
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static DateTime TruncateMs(this DateTime dateTime)
```

# ListenerHelper

```C#
/// <summary>Получить старое значение</summary>
/// <typeparam name="T">Тип</typeparam>
/// <param name="event">Событие</param>
/// <param name="propertyName">Название свойства</param>
/// <returns>Старое значение</returns>
public static T GetOldValue<T>(PostUpdateEvent @event, string propertyName)
```

```C#
/// <summary>Получить старое значение</summary>
/// <typeparam name="T">Тип</typeparam>
/// <param name="event">Событие</param>
/// <param name="propertyName">Название свойства</param>
/// <returns>Старое значение</returns>
public static T GetOldValue<T>(PreUpdateEvent @event, string propertyName)
```

```C#
/// <summary>Получить значение свойства</summary>
/// <typeparam name="T">Тип</typeparam>
/// <param name="event">Событие</param>
/// <param name="propertyName">Название свойства</param>
/// <returns>Значение</returns>
public static T GetValue<T>(PostUpdateEvent @event, string propertyName)
```

```C#
/// <summary>Присвоить значение свойству</summary>
/// <param name="event">Событие</param>
/// <param name="propertyName">Название свойства</param>
/// <param name="value">Значение</param>
public static void SetValue(PreUpdateEvent @event, string propertyName, object value)
```

```C#
/// <summary>Получить значение свойства</summary>
/// <typeparam name="T">Тип</typeparam>
/// <param name="event">Событие</param>
/// <param name="propertyName">Название свойства</param>
/// <returns>Значение</returns>
public static T GetValue<T>(PostInsertEvent @event, string propertyName)
```

```C#
/// <summary>Присвоить значение свойству</summary>
/// <param name="event">Событие</param>
/// <param name="propertyName">Название свойства</param>
/// <param name="value">Значение</param>
public static void SetValue(PreInsertEvent @event, string propertyName, object value)
```

# ServerHelper

```C#
/// <summary>
/// Получить среднее время запуска сервера
/// </summary>
public static string AverageStartTime
```

```C#
/// <summary>
/// Версия компонентов IEE
/// </summary>
public static string IEEVersion
```

```C#
/// <summary>
/// Возвращает признак что система в режиме тестирования
/// </summary>
public static bool IsTesting
```

```C#
/// <summary>
/// Перезапустить сервер
/// </summary>
public static void RestartServer()
```

```C#
/// <summary>
/// Логгер импорта
/// </summary>
public static ILog ImportLogger
```

```C#
/// <summary>
/// Запись в протокол импорта данных в справочник
/// </summary>
/// <param name="name">Имя справочника</param>
/// <param name="total">Всего записей</param>
/// <param name="new">Новых записей</param>
/// <param name="update">Обновлений</param>
/// <param name="delete">Удалений</param>
public static void ImportLogCatalog(string name, int total, int @new = 0, int update = 0, int delete = 0)
```

```C#
/// <summary>
/// Запись в протокол импорта что данные для справочника отсутствуют
/// </summary>
/// <param name="name">Имя справочника</param>
public static void ImportLogCatalogNone(string name)
```

```C#
/// <summary>
/// Время работы сервера
/// </summary>
/// <returns></returns>
public static TimeSpan UpTime
```

```C#
/// <summary>
/// Логгер обновления БД
/// </summary>
public static ILog DbUpdateLogger
```

```c#
/// <summary>
/// Логер пакетной обработки
/// </summary>
public static ILog ProcessLogger
```

# UserHelper

```C#
/// <summary>
/// Системный пользователь
/// </summary>
public static IUser SystemUser
```

```C#
/// <summary>
/// Пользователь подсистемы обмена
/// </summary>
public static IUser ExchangeUser
```

```C#
/// <summary>
/// Получить непосредственного руководителя для пользователя
/// </summary>
/// <param name="user">Пользователь</param>
/// <returns>Руководитель (если есть, иначе null)</returns>
public static IUser GetImmediateChiefByUser(IUser user)
```

```C#
/// <summary>
/// Получить всех активных пользователей группы
/// </summary>
/// <param name="gUidStr">Строковый Uid группы</param>
/// <returns>Список пользователей</returns>
public static IEnumerable<IUser> GetUsersInGroup(string gUidStr)
```

```C#
/// <summary>
/// Получить всех активных пользователей группы
/// </summary>
/// <param name="gUid">Uid группы</param>
/// <returns>Список пользователей</returns>
public static IEnumerable<IUser> GetUsersInGroup(Guid gUid)
```

```C#
/// <summary>
/// Преобразовать полные инициалы в сокращенные
/// </summary>
/// <param name="fullName">Полные инициалы</param>
/// <param name="lastNameOrder">Фамилия в начале</param>
/// <returns>Сокращенные инициалы</returns>
public static string ConvertToShortName(string fullName, bool lastNameOrder = true)
```

```C#
/// <summary>
/// Входит ли пользователь в группу Администраторы
/// </summary>
/// <returns></returns>
public static bool IsAdmin
```

<p class="callout info">Только ELMA4</p>

```c#
/// <summary>
/// Отправить push уведомление текущему пользователю
/// </summary>
/// <param name="title">Заголовок уведомления</param>
/// <param name="message">Сообщение</param>
/// <param name="clickUrl">Ссылка в браузере при нажатии</param>
/// <param name="tag">Тэг (для группировки)</param>
/// <returns></returns>
public static bool WebPush(string message, string title = null, string clickUrl = null, string tag = null)
```

```c#
/// <summary>
/// Отправить push уведомление пользователю
/// </summary>
/// <param name="user">Пользователь</param>
/// <param name="title">Заголовок уведомления</param>
/// <param name="message">Сообщение</param>
/// <param name="clickUrl">Ссылка в браузере при нажатии</param>
/// <param name="tag">Тэг (для группировки)</param>
/// <returns></returns>
public static bool WebPush(IUser user, string message, string title = null, string clickUrl = null, string tag = null)
```

# WorkflowHelper

```C#
/// <summary>
/// Получить запускаемые процессы по входному типу сущности
/// </summary>
/// <param name="typeUid">UID типа сущности</param>
/// <returns>Список процессов Workflow</returns>
public static IEnumerable<IWorkflowProcess> GetStartableProcessesByInputEntity(Guid typeUid)
```

```C#
/// <summary>
/// Комментарий для процессной задачи
/// </summary>
/// <param name="element">Uid элемента задачи на схеме</param>
/// <param name="task">Задача</param>
/// <param name="author">Автор комментария</param>
/// <param name="date">Дата комментария</param>
/// <param name="comment">Комментарий</param>
/// <param name="type">Тип комментария</param>
public static void CommentForTask(Guid element, ITaskBase task, IUser author, DateTime date, string comment, EOWorkflowTaskCommentType type = EOWorkflowTaskCommentType.Info)
```

```C#
/// <summary>
/// Сохранить уведомление для последующего показа
/// </summary>
/// <param name="message">Текст сообщения</param>
/// <param name="type">Тип сообщения</param>
public static void Notify(string message, NotifyType type = NotifyType.Info)
```

```C#
/// <summary>
/// Убрать замещение у задачи процесса
/// </summary>
/// <param name="task">Задача процесса</param>
/// <param name="uid">Uid задачи на схеме</param>
/// <param name="ignoreUsers">Список игнорируемых пользователей</param>
public static void RemoveReplacement([NotNull] this ITaskBase task, Guid uid, IEnumerable<IUser> ignoreUsers = null)
```

```C#
/// <summary>
/// Завершить процесс с выводом ошибки пользователю
/// </summary>
/// <param name="context"></param>
/// <param name="error"></param>
public static void CompleteWithError(object context, string error)
```

```C#
/// <summary>
/// Выполнить длительное действие в отдельном потоке при создании задачи
/// </summary>
/// <param name="element">Id задачи на диаграмме</param>
/// <param name="task">Созданная задача в OnTaskCreate</param>
/// <param name="action">Действие</param>
public static void ProcessLongAction(Guid element, ITaskBase task, Action<LongActionModel, ISession> action)
```

# Модели

# LongActionModel

```C#
public class LongActionModel
{
	/// <summary>
  	/// Логгер
  	/// </summary>
  	public ILog Logger { get; }

  	/// <summary>
  	/// Контекст процесса
  	/// </summary>
  	public WorkflowInstanceContext Context { get; }

  	/// <summary>
  	/// Начало обработки
  	/// </summary>
  	public DateTime StartDate { get; }

  	/// <summary>
  	/// Окончание обработки
  	/// </summary>
  	public DateTime? EndDate { get; set; }

  	/// <summary>
  	/// Название действия
  	/// </summary>
  	public string Name { get; set; }

  	/// <summary>
  	/// Задача бизнес процесса
  	/// </summary>
	public IWorkflowTaskBase Task { get; set; }

  	/// <summary>
  	/// Кнопки переходов
  	/// </summary>
  	public IEnumerable<string> Buttons { get; set; }

  	/// <summary>
  	/// Ошибка выполнения
  	/// </summary>
  	public string Error { get; set; }

  	/// <summary>
  	/// Обновить задачу в браузере после завершения длительной операции
  	/// </summary>
  	public bool RefreshOnCompleted { get; set; }
}
```

# Пример HTML брэнд шаблона

<p class="callout info">ELMA4</p>

```HTML
<div style="font-family: Arial, Helvetica, sans-serif; font-weight: 100;">
	<table style="border-spacing: 0px; border: 0; width: 100%; border-collapse: collapse;">
		<tr style="background: #ffdddd; text-align: right;">
			<td style="width: 1%; padding: 10px;">
				<img src="https://www.elmastore.ru/img/emaillogo.png" width="128"/>
			</td>
			<td style="width: 99%; padding: 15px;  vertical-align: top;">
				<span style="font-size: 2.0em;">Название Вашей системы</span>
			</td>
		</tr>
		<tr>
			<td colspan="2" style="background: #6495ed; font-size: 1.2em; padding: 10px;">
				{$Body}
			</td>
		</tr>
		<tr>
			<td colspan="2" style="color: gray; font-size: 0.7em; padding-top: 10px;">
				УВЕДОМЛЕНИЕ О КОНФИДЕНЦИАЛЬНОСТИ: Это электронное сообщение и любые документы, приложенные к нему, содержат конфиденциальную информацию. Настоящим уведомляем Вас о том, что если это сообщение не предназначено Вам, использование, копирование, распространение информации, содержащейся в настоящем сообщении, а также осуществление любых действий на основе этой информации, строго запрещено. Если Вы получили это сообщение по ошибке, пожалуйста, сообщите об этом отправителю по электронной почте и удалите это сообщение.
			</td>
		</tr>
	</table>
</div>
```

Результатом будет:

[![image-1627196011610.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-07/scaled-1680-/sjddQ9AkcC9R9EMH-image-1627196011610.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-07/sjddQ9AkcC9R9EMH-image-1627196011610.png)

# Примеры

Полезные примеры использования элементов компонента

# Реализация обработки длительной операции в задаче

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

<p class="callout warning">Внутри обработчика нельзя использовать **context**. Вместо этого использовать **model.Context**.</p>

```C#
public override void OnTaskCreate(ITaskBase task, P_IA_AandP context)
{
	// 4f55dd98-5242-4633-88e8-4c745551b52c - Id задачи на диаграмме 
	WorkflowHelper.ProcessLongAction(new Guid("4f55dd98-5242-4633-88e8-4c745551b52c"), task, (model, session) =>
	{
		model.Name = "Сбор данных по активным задачам"; // Название операции, отображаемое в задаче
      
		// Здесь любой код для длительных операций
      	var instanceContext = model.Context as P_IA_AandP;
    });
}
```

В ELMA4 изменена сигнатура вызова метода - дополнительно передается тип контекста:

```c#
public override void OnTaskCreate(ITaskBase task, P_IA_AandP context)
{
	// 4f55dd98-5242-4633-88e8-4c745551b52c - Id задачи на диаграмме 
	WorkflowHelper.ProcessLongAction<P_IA_AandP>(new Guid("4f55dd98-5242-4633-88e8-4c745551b52c"), task, (model, session) =>
	{
		model.Name = "Сбор данных по активным задачам"; // Название операции, отображаемое в задаче
      
		// Здесь любой код для длительных операций
            
      	var instanceContextVar1 = model.Context.Var1;
    });
}
```

<p class="callout info">После окончания длительной операции контекст процесса сохранять не нужно - это будет сделано автоматически, если были изменения.</p>

В результате при создании задачи запуститься в отдельном потоке обработка, кнопки переходов будут скрыты и будет отображена информация об операции со счетчиком времени выполнения. Например:

[![image-1630422165056.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-08/scaled-1680-/dfTqQbz7ac1KFkcB-image-1630422165056.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-08/dfTqQbz7ac1KFkcB-image-1630422165056.png)

После завершения длительно операции кнопки переходов вновь появятся и будет выдано сообщение со временем завершения выполнения указанной операции:

[![image-1630423575147.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-08/scaled-1680-/m4ScKjegHhuoWMxk-image-1630423575147.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-08/m4ScKjegHhuoWMxk-image-1630423575147.png)

Если в исполняемом коде будет вызвано исключение, то появится соответствующее сообщение, кнопки переходов так же появятся:

[![image-1630427822285.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-08/scaled-1680-/Z5jfGKLNsIHYJZZe-image-1630427822285.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-08/Z5jfGKLNsIHYJZZe-image-1630427822285.png)

# Скрытие/отображение кнопок переходов в процессных задачах

<p class="callout success">Начиная с 4.0.28 и 3.15.48</p>

<p class="callout warning">На текущий момент ELMA4 Р2 не поддерживается</p>

Для скрытия или отображения в сценарии процесса кнопок перехода в задачах можно использовать метод form.HideConnector:

```C#
using ITino.ELMA.Common.Helpers;

/// <summary>
/// Скрыть кнопку перехода на какое то изменение
/// </summary>
/// <param name="context">Контекст процесса</param>
/// <param name="form"></param>
public virtual void HideOnChange (Context context, EleWise.ELMA.Model.Views.FormViewBuilder<Context> form)
{
  	// Что то меняем в значениях контекста процесса (для примера)
	context.Two = context.One;
  
	// Скрыть кнопку перехода, где Uid перехода на диаграмме процесса  
	form.HideConnector(new Guid("b29c9cc7-7068-4213-a6e3-987171a702aa"));
}

/// <summary>
/// Показать ранее скрытую кнопку перехода на какое то изменение
/// </summary>
/// <param name="context">Контекст процесса</param>
/// <param name="form"></param>
public virtual void ShowOnChange (Context context, EleWise.ELMA.Model.Views.FormViewBuilder<Context> form)
{
	// Показать кнопку перехода, где Uid перехода на диаграмме процесса  
	form.HideConnector(new Guid("b29c9cc7-7068-4213-a6e3-987171a702aa"), false);
}
```

Все кнопки доступные изначально:

[![image-1639307960009.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-12/scaled-1680-/2M7y6PYx6ZMG7hTq-image-1639307960009.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-12/2M7y6PYx6ZMG7hTq-image-1639307960009.png)

И для ELMA4:

[![image-1639308155429.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-12/scaled-1680-/cSBkKI98vClE1kEe-image-1639308155429.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-12/cSBkKI98vClE1kEe-image-1639308155429.png)

После выполнения скрипта, если есть хоть один скрытый переход, то в верхнем меню убираем **Сделано**, т.к. невозможно динамически определить в нем нужных переход:

[![image-1639308073033.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-12/scaled-1680-/a1d0aNAvKYpuGsrw-image-1639308073033.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-12/a1d0aNAvKYpuGsrw-image-1639308073033.png)

И для ELMA4:[![image-1639308216480.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-12/scaled-1680-/XOwWXDt17qTiim9J-image-1639308216480.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-12/XOwWXDt17qTiim9J-image-1639308216480.png)

# Кворум сервисы

Распределенные отказоустойчивые сервисы в кластере Корпоративной редакции системы

# Мониторинг

<p class="callout warning">Доступно только в Корпоративной редакции</p>

Статус установленных кворум сервисов кластера отображается по ссылке [**Администрирование - Система - Кворум сервисы**](https://kb.randmgroup.ru/books/spravka-elma4/page/kvorum-servisy "Кворум сервисы"):

[![image-1635059448252.png](https://kb.randmgroup.ru/uploads/images/gallery/2021-10/scaled-1680-/EolhOcFJJ6RercZQ-image-1635059448252.png)](https://kb.randmgroup.ru/uploads/images/gallery/2021-10/EolhOcFJJ6RercZQ-image-1635059448252.png)

Информация о текущих пакетах на обработку кворум сервисами находиться по ссылке **Справочники - Элемент очереди кворумного сервиса**.

# Менеджеры

# CounterManager

```c#
public class CounterManager : EntityManager<ICOCounter, long>
{
    /// <summary>
    /// instance
    /// </summary>
    public new static CounterManager Instance => Locator.GetServiceNotNull<CounterManager>();

    /// <summary>
    /// Загрузить или создать новый
    /// </summary>
    /// <param name="uid">Уникальный идентификатор счетчика</param>
    /// <param name="name">Название</param>
    /// <param name="mask">Маска в формате string.Format</param>
    /// <returns></returns>
    public ICOCounter LoadOrCreate(Guid uid, string name = null, string mask = null);

    /// <summary>
    /// Получить новое значение по максе
    /// </summary>
    /// <param name="counter">Счетчик</param>
    /// <param name="save">Сохранить в базе</param>
    /// <param name="checkYear">Сбросить для нового года</param>
    /// <param name="args">Дополнительные аргшуметы для маски</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public string FormatNewValue(ICOCounter counter, bool save = true, bool checkYear = false, params object[] args);

    /// <summary>
    /// Получить новое значение
    /// </summary>
    /// <param name="counter">Счетчик</param>
    /// <param name="save">Сохранить в базе</param>
    /// <param name="checkYear">Сбросить для нового года</param>
    /// <returns></returns>
    /// <exception cref="ArgumentException"></exception>
    [Transaction]
    public virtual long GetNewId(ICOCounter counter, bool save = true, bool checkYear = false);
}
```