Пользовательские действия
Администратор базы данных может расширить функциональность сервера, создав собственное действие для применения в сценариях автоматизации.
Для этого необходимо ознакомиться с разделом SDK для серверных модулей расширения документации SDK. С помощью серверного API администратор может:
- Создать или отредактировать существующее действие во встроенном в Pilot-myAdmin интерпретаторе C#.
- Написать серверный модуль расширения.
Добавление пользовательского действия в интерпретаторе C#
API, доступное в C#-скриптах, полностью соответствует API модулей расширения Pilot-Server (Ascon.Pilot.ServerExtensions.SDK).
Использование сторонних библиотек в C#-скриптах автоматизации недоступно. Если необходимо использовать сторонние библиотеки вместо C#-скриптов, используйте серверные модули расширения.
Для добавления пользовательского действия в Pilot-myAdmin:
- выберите базу данных;
- откройте вкладку Автоматизация;
- нажмите Создать сценарий и задайте имя нового сценария;
- перейдите на вкладку C# в правой части экрана;
- добавьте код;
- нажмите Сохранить изменения.
После этого созданное действие можно использовать в сценарии автоматизации на вкладке JSON.
Действия, объявленные в C#-скриптах, могут быть вызваны только с помощью серверных триггеров ("triggerType": "Server"). |
Для отладки C#-скрипта может быть использована Visual Studio. Для этого необходимо:
- включить возможность отладки C#-скриптов в settings.xml: <EnableCSharpScriptDebug>true</EnableCSharpScriptDebug>;
- добавить в нужное место C#-скрипта инструкцию System.Diagnostics.Debugger.Break();
- сохранить скрипт;
- подключиться к процессу Pilot-Server через отладчик Visual Studio;
- инициировать триггер, запускающий отлаживаемое действие.
При срабатывании точки останова в Visual Studio будет открыт исходный код скрипта со всеми возможностями отладки.
Пример пользовательского действия
В качестве примера используем действие UpdateDocumentStateOnSigning, которое при подписании документа изменит его состояние. Для активизации действия необходимо :
- Подключить действие:
- Вариант 1: добавить код действия в интерпретатор C# в Pilot-myAdmin.
- Вариант 2: подключить модуль ServerActivitySample.
- Создать атрибут UserState типа Состояние для типа Документ.
- Добавить сценарий автоматизации UpdateDocumentStateOnSigning.
1. Вариант 1. Добавление кода действия в интерпретатор С#
Выполните шаги, описанные выше. Добавьте следующий код:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading.Tasks;
using Ascon.Pilot.DataClasses;
using Ascon.Pilot.DataModifier;
using Ascon.Pilot.ServerExtensions.SDK;
using Newtonsoft.Json;
[Export(typeof(IServerActivity))]
public class UpdateDocumentStateOnSigning : IServerActivity
{
public string Name => "UpdateDocumentStateOnSigning";
public const string StateAttributeName = "stateAttributeName";
public const string SignedStateName = "signedStateName";
public const string NotSignedStateName = "notSignedStateName";
public const string StatesFrom = "statesFrom";
public Task RunAsync(IModifierBase modifier, IModifierBackend backend, IServerActivityContext serverActivityContext, IAutomationEventContext automationEventContext)
{
var notifier = serverActivityContext.GetService<IAutomationNotifier>();
var source = automationEventContext.Source;
var isFullySigned = IsFullySigned(source);
if(!serverActivityContext.Params.TryGetValue(StateAttributeName, out var stateAttributeName))
throw new InvalidOperationException($"{StateAttributeName} parameter is not defined in UpdateDocumentStateOnSigning activity");
if(!serverActivityContext.Params.TryGetValue(SignedStateName, out var signedStateName))
throw new InvalidOperationException($"{SignedStateName} parameter is not defined in UpdateDocumentStateOnSigning activity");
if(!serverActivityContext.Params.TryGetValue(NotSignedStateName, out var notSignedStateName))
throw new InvalidOperationException($"{NotSignedStateName} parameter is not defined in UpdateDocumentStateOnSigning activity");
serverActivityContext.Params.TryGetValue(StatesFrom, out var statesFrom);
if (statesFrom != null && statesFrom is IEnumerable list)
{
var statesFromArray = JsonConvert.DeserializeObject<List<string>>(statesFrom.ToString());
var currentState = backend.GetMetadata().UserStates
.FirstOrDefault(x => x.Id == source.Attributes[(string) stateAttributeName]);
if (currentState == null || !statesFromArray.Contains(currentState.Name))
return Task.CompletedTask;
}
var signedState = backend.GetMetadata().UserStates.FirstOrDefault(x => x.Name == (string)signedStateName);
if(signedState == null)
throw new InvalidOperationException($"State {signedStateName} not found");
var notSignedState = backend.GetMetadata().UserStates.FirstOrDefault(x => x.Name == (string)notSignedStateName);
if(notSignedState == null)
throw new InvalidOperationException($"State {NotSignedStateName} not found");
var newStateId = isFullySigned ? signedState.Id : notSignedState.Id;
if (!source.Attributes.TryGetValue((string)stateAttributeName, out var stateAttributeValue) || stateAttributeValue.GuidValue != newStateId)
{
modifier.EditObject(source).SetAttribute((string)stateAttributeName, newStateId);
NotifySigned(notifier, backend, source);
}
return Task.CompletedTask;
}
public static bool IsFullySigned(INObject @object)
{
var xpsFile = @object.ActualFileSnapshot.Files.FirstOrDefault(f => f.Name.EndsWith(".xps", StringComparison.InvariantCultureIgnoreCase));
if (xpsFile == null || xpsFile.SignatureRequests.Count == 0)
return false;
var requestCount = xpsFile.SignatureRequests.Count;
var signCount = xpsFile.SignatureRequests.Count(signature => signature.Signs.Any());
return signCount == requestCount;
}
private void NotifySigned(IAutomationNotifier notifier, IModifierBackend backend, INObject document)
{
var xpsFile = document.ActualFileSnapshot.Files.FirstOrDefault(f => f.Name.EndsWith(".xps", StringComparison.InvariantCultureIgnoreCase));
if (xpsFile == null || xpsFile.SignatureRequests.Count == 0)
return;
var positionsToNotify = new List<int>();
foreach (var signature in xpsFile.SignatureRequests.Where(x => x.Signs.Any()))
{
var relatedTaskId = signature.ObjectId;
var relatedTask = backend.GetObject(relatedTaskId);
if(relatedTask == null || !relatedTask.Attributes.TryGetValue(SystemTaskAttributes.INITIATOR_POSITION, out var initiatorValue))
continue;
if(initiatorValue != null && initiatorValue.ArrayIntValue != null)
positionsToNotify.AddRange(initiatorValue.ArrayIntValue);
}
var peopleToNotify = positionsToNotify.SelectMany(backend.GetPeopleOnPosition);
foreach (var person in peopleToNotify)
{
notifier.Notify(person.Id, document, "Документ согласован", Name);
}
}
}
1. Вариант 2. Подключение серверного модуля расширения
Действие UpdateDocumentStateOnSigning реализовано в примере серверного модуля расширения ServerActivitySample. Его можно найти в комплекте SDK (..\Server\Extensions\Samples\bin).
Серверные модули расширения подключаются в клиентском приложении с помощью меню Сервис → Настройки → Модули расширения. Подробнее здесь.
Если серверный модуль загружен из Интернета, необходимо выполнить разблокировку загруженных файлов. Откройте свойства файлов и, в зависимости от операционной системы, нажмите кнопку или выберите опцию Разблокировать. |
В Pilot-myAdmin остановите базу данных и снова её запустите. Также можно перезапустить службу PilotServer.
Если всё было сделано правильно, то в разделе Серверные модули расширения вкладки Общая информация Вы увидите наименование установленного модуля ServerActivitySample, а также его версию и дату изменения.
2. Добавление атрибута
Для работы пользовательского скрипта UpdateDocumentStateOnSigning в конфигурации базы данных необходим тип Документ (document) с атрибутом approvedState типа Состояние, в группе состояний которого должны присутствовать состояния none и approved с возможностью перехода как от none к approved, так и от approved к none. Для этого:
- В Pilot-myAdmin откройте нужную базу данных, перейдите на вкладку Типы.
- Для типа Документ (document) создайте атрибут типа Состояние с именем approvedState.
- При создании атрибута в выпадающем списке Группа состояний выберите Управление группами состояний.
- В открывшемся окне создайте новую группу состояний с именем, например, Approvation.
- Нажмите кнопку Выбор Состояний. Отметьте галочкой состояние Согласовано (approved) и нажмите Выбрать.
- Выделите состояние Нет. Нажмите кнопку Создать переход.
- Будет создан переход из состояния Нет в состояние Согласовано.
- Аналогичным образом создайте переход из состояния Согласовано в состояние Нет.
3. Добавление сценария автоматизации
В myAdmin откройте нужную базу данных, перейдите на вкладку Автоматизация и нажмите кнопку Создать сценарий. Задайте имя сценария, например ServerActivitySample. Скопируйте приведённый ниже скрипт, вставьте его в поле вкладки JSON справа и нажмите Сохранить изменения.
[
{
"when": "DocumentSignatureRequestChanged",
"params": {
"triggerType": "Server",
"sourceTypes": [
"document"
]
},
"then": [
{
"activity": "UpdateDocumentStateOnSigning",
"params": {
"stateAttributeName": "approvedState",
"signedStateName": "approved",
"notSignedStateName": "none"
}
}
]
},
{
"when": "VersionChanged",
"params": {
"triggerType": "Server",
"sourceTypes": [
"document"
]
},
"then": [
{
"activity": "UpdateDocumentStateOnSigning",
"params": {
"stateAttributeName": "approvedState",
"signedStateName": "approved",
"notSignedStateName": "none"
}
}
]
},
{
"when": "DocumentSigned",
"params": {
"triggerType": "Server",
"sourceTypes": [
"document"
]
},
"then": [
{
"activity": "UpdateDocumentStateOnSigning",
"params": {
"stateAttributeName": "approvedState",
"signedStateName": "approved",
"notSignedStateName": "none"
}
}
]
}
]
Cкрипт будет запускать добавленное действие UpdateDocumentStateOnSigning при наступлении одного из трёх событий:
- Изменились запросы на подпись документа (Сработал триггер DocumentSignatureRequestChanged) .
- Была создана новая версия документа (Сработал триггер VersionChanged).
- Документ был подписан (Сработал триггер DocumentSigned).
При наступлении любого из вышеперечисленных событий запустится действие UpdateDocumentStateOnSigning, которое установит состояние Согласовано (approved), если документ подписан, или состояние <Нет> (none), если документ не подписан.
Параметр statesFrom
Работу действия можно ограничить списком состояний, указанных в необязательном параметре statesFrom, из которых перевод состояния может быть осуществлён:
"{состояние_1}",
"{состояние_2}",
...
"{состояние_n}",
],
Если параметра statesFrom в скрипте нет, или список пуст, то переход осуществляется всегда.
Пример:
Документ вложили в процесс согласования с двумя согласующими. Первый согласующий не подписывает, ставит состояние Не согласовано. Такое же состояние ставится и на документе. Второй подписывает, и документу ставится состояние Согласовано, что не является правильным, ведь первый согласующий уже отказался его подписывать.
Для решения возникшей ситуации, нужно добавить в скрипт параметр statesFrom, перечислив в списке все состояния, кроме Не согласовано. В этом случае, после действий второго согласовывающего, документ останется не согласованным.