Пользовательские действия

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

Для этого необходимо ознакомиться с разделом SDK для серверных модулей расширения документации SDK. С помощью серверного API администратор может:

  1. Создать или отредактировать существующее действие во встроенном в Pilot-myAdmin интерпретаторе C#.
  2. Написать серверный модуль расширения.

Добавление пользовательского действия в интерпретаторе C#

API, доступное в C#-скриптах, полностью соответствует API модулей расширения Pilot-Server (Ascon.Pilot.ServerExtensions.SDK).

Использование сторонних библиотек в C#-скриптах автоматизации недоступно. Если необходимо использовать сторонние библиотеки вместо C#-скриптов, используйте серверные модули расширения.

Для добавления пользовательского действия в Pilot-myAdmin:

  • выберите базу данных;
  • откройте вкладку Автоматизация;
  • нажмите Создать сценарий и задайте имя нового сценария;
  • перейдите на вкладку C# в правой части экрана;
  • добавьте код;
  • нажмите Сохранить изменения.

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

Действия, объявленные в C#-скриптах, могут быть вызваны только с помощью серверных триггеров ("triggerType": "Server").

Для отладки C#-скрипта может быть использована Visual Studio. Для этого необходимо:

  1. включить возможность отладки C#-скриптов в settings.xml: <EnableCSharpScriptDebug>true</EnableCSharpScriptDebug>;
  2. добавить в нужное место C#-скрипта инструкцию System.Diagnostics.Debugger.Break();
  3. сохранить скрипт;
  4. подключиться к процессу Pilot-Server через отладчик Visual Studio;
  5. инициировать триггер, запускающий отлаживаемое действие.

При срабатывании точки останова в Visual Studio будет открыт исходный код скрипта со всеми возможностями отладки.

Пример пользовательского действия

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

  1. Подключить действие:
    • Вариант 1: добавить код действия в интерпретатор C# в Pilot-myAdmin.
    • Вариант 2: подключить модуль ServerActivitySample.
  2. Создать атрибут UserState типа Состояние для типа Документ.
  3. Добавить сценарий автоматизации 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, из которых перевод состояния может быть осуществлён:

"statesFrom": [
    "{состояние_1}",
    "{состояние_2}",
    ...
    "{состояние_n}",
],

Если параметра statesFrom в скрипте нет, или список пуст, то переход осуществляется всегда.

Пример:

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

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