Перейти к содержанию

Новое событие с номером, вызываемое из бизнес-процесса

Новое событие с номером, вызываемое из бизнес-процесса

Предположим, что есть определённый скрипт бизнес-процесса или тип этапа маршрута, в котором надо выполнить действие с номерами в контексте INumberDirector, например, зарезервировать и выделить номер типа NumberTypes.Primary в соответствии с настройками нумерации для типа документа.

Для этого требуется определить новый идентификатор NumberEventType и связать его с методом в NumberDirector. Идентификатор определим в сборке Tessa.Extensions.Shared в статическом классе MyEventTypes:

using System; using Tessa.Cards.Numbers;

namespace Tessa.Extensions.Shared.Numbers { public static class MyEventTypes { public static readonly NumberEventType CustomEvent = new NumberEventType(new Guid("уникальный ID для этого события"), nameof(CustomEvent));

public static void Register(INumberEventTypeRegistry registry) { registry.Register(CustomEvent); } } }

Бизнес-логика действия с номером определяется в классе-наследнике DocumentNumberDirector, в котором мы добавим новый метод NotifyOnCustomEvent. Класс также добавим в сборку Tessa.Extensions.Shared.

using System.Threading; using System.Threading.Tasks; using Tessa.Cards; using Tessa.Cards.Numbers; using Tessa.Extensions.Default.Shared.Numbers; using Tessa.Extensions.Default.Shared.Workflow.KrProcess; using Tessa.Platform;

namespace Tessa.Extensions.Shared.Numbers { public class MyNumberDirector : DocumentNumberDirector { public MyNumberDirector(IKrTypesCache typesCache, INumberDependencies dependencies) : base(typesCache, dependencies) { }

public Task<bool> NotifyOnCustomEventAsync( INumberContext context, CancellationToken cancellationToken = default) => this.NotifyOnEventAsync( context, MyEventTypes.CustomEvent, this.OnCustomEventAsync, this.BeforeCustomEventAsync, cancellationToken);

protected virtual Task<bool> BeforeCustomEventAsync( INumberContext context, CancellationToken cancellationToken = default) => TaskBoxes.True;

protected virtual async Task<bool> OnCustomEventAsync( INumberContext context, CancellationToken cancellationToken = default) { // здесь размещается бизнес-логика для нашего события, см. по аналогии // с методами DocumentNumberDirector.OnRegisteringCard и др.

INumberObject reservedNumber = await context.Builder.ReserveAndCommitAtServerAsync( context, NumberTypes.Primary, cancellationToken: cancellationToken).ConfigureAwait(false);

return !reservedNumber.IsEmpty(); }

protected override async ValueTask<bool> IsAvailableCoreAsync( INumberContext context, NumberEventType eventType, CancellationToken cancellationToken = default) { // здесь можно дополнительно указать предикат - проверки перед тем, как будет вызвано событие, // чтобы заблокировать его вызов в каких-то ситуациях

// в этом примере указывается, что запрещено вызывать событие при первом сохранении карточки; // другие примеры см. в базовой реализации в DocumentNumberDirector

// если такие проверки не нужны, то не переопределяйте метод IsAvailableCore

if (!await base.IsAvailableCoreAsync(context, eventType, cancellationToken).ConfigureAwait(false)) { return false; }

if (eventType == MyEventTypes.CustomEvent && context.Card.StoreMode == CardStoreMode.Insert) { return false; }

return true; } } }

Зарегистрируем тип события, а также класс MyNumberDirector, который будет функционировать для типов карточки Contract (“Договор”) и Outgoing (“Исходящий”).

using System; using Tessa.Cards.Numbers; using Tessa.Platform; using Unity; using Unity.Lifetime;

namespace Tessa.Extensions.Shared.Numbers { [Registrator] public sealed class Registrator : RegistratorBase { private static readonly Guid[] ids = { // Contract new Guid("335f86a1-d009-012c-8b45-1f43c2382c2d") };

public override void InitializeRegistration() => MyEventTypes.Register(NumberEventTypeRegistry.Instance);

public override void RegisterUnity() { this.UnityContainer .RegisterType<MyNumberDirector>(new ContainerControlledLifetimeManager()); }

public override void FinalizeRegistration() { var container = this.UnityContainer.TryResolve<INumberDirectorContainer>(); if (container is not null) { foreach (Guid cardTypeID in ids) { container.Register(cardTypeID, c => c.Resolve<AdditionalNumberDirector>()); } } } } }

Теперь требуется вызвать метод MyNumberDirector.NotifyOnCustomEvent из другого места системы, передав ему требуемые зависимости. Рассмотрим ситуацию, когда действие с номером выполняется в новом типе этапа при завершении задания на этом этапе.

Для типа этапа в сборке Tessa.Extensions.Shared указывается дескриптор типа этапа с настройками, которые система маршрутов использует для этого типа. Также здесь обязательно добавить строку в таблицу KrProcessStageTypes, см. комментарий.

using System; using Tessa.Extensions.Default.Shared.Workflow; using Tessa.Extensions.Default.Shared.Workflow.KrProcess;

namespace Tessa.Extensions.Shared.Numbers { public static class MyStageTypeDescriptors { // продублируйте значения ID и Caption этого типа этапа в таблице KrProcessStageTypes, // добавив строку в таблицу для проектной библиотеки схемы

public static readonly StageTypeDescriptor MyStageDescriptor = StageTypeDescriptor.Create(b => { b.ID = new Guid("уникальный ID для этого типа этапа"); b.Caption = "Мой этап"; b.SettingsCardTypeID = new Guid("ID типа карточки с настройками этапа"); b.PerformerUsageMode = PerformerUsageMode.Multiple; b.PerformerIsRequired = true; b.PerformerCaption = "$UI_KrPerformersSettings_Approvers"; b.CanOverrideAuthor = true; b.UseTimeLimit = true; b.CanOverrideTaskHistoryGroup = true; b.SupportedModes.Add(KrProcessRunnerMode.Async); }); } }

Note

Для типа этапа в сборке Tessa.Extensions.Shared также можно указать объект-форматтер, посредством которого заполняется колонка “Настройки” в таблице этапов, в зависимости от указанных настроек. Для desktop-клиента форматтер будет вызван сразу после закрытия редактируемой строки, для web-клиента - на сервере при сохранении карточки с маршрутом. Если форматтер не указан, то поле с настройками будет пустым. Пример форматтера доступен в классе RegistrationStageTypeFormatter, а его регистрация в классе StageTypeFormattersRegistrator.

Для типа этапа задаётся обработчик - класс-наследник StageTypeHandlerBase, который система маршрутов вызывает при действиях с этапом. Добавьте такой класс в Tessa.Extensions.Server.

using System.Threading.Tasks; using Tessa.Cards; using Tessa.Cards.Extensions; using Tessa.Cards.Numbers; using Tessa.Extensions.Default.Server.Workflow.KrProcess.Scope; using Tessa.Extensions.Default.Server.Workflow.KrProcess.Workflow.Handlers;

namespace Tessa.Extensions.Server.Numbers { public class MyStageTypeHandler : StageTypeHandlerBase { public MyStageTypeHandler(INumberDirectorContainer numberContainer, IKrScope krScope, ICardMetadata cardMetadata) { this.NumberContainer = numberContainer; this.KrScope = krScope; this.CardMetadata = cardMetadata; }

protected INumberDirectorContainer NumberContainer { get; set; }

protected IKrScope KrScope { get; set; }

protected ICardMetadata CardMetadata { get; set; }

// переопределите здесь другие методы, которые отправляют задания, // по аналогии с RegistrationStageTypeHandler

public override StageHandlerResult HandleTaskCompletion(IStageTypeHandlerContext context) => this.HandleTaskCompletionAsync(context).GetAwaiter().GetResult();

private async Task<StageHandlerResult> HandleTaskCompletionAsync(IStageTypeHandlerContext context) { if (context.MainCardID.HasValue && context.MainCardTypeID.HasValue) { CardType cardType = (await this.CardMetadata.GetCardTypesAsync())[context.MainCardTypeID.Value];

// если для типа карточки, в котором задействован этап, зарегистрирован MyNumberDirector // в качестве объекта для взаимодействия с номерами, то подготовим контекст INumberDirectorProvider numberProvider = this.NumberContainer.GetProvider(cardType.ID); INumberDirector numberDirector = numberProvider.GetDirector(); Card mainCard = this.KrScope.GetMainCard(context.MainCardID.Value);

INumberComposer numberComposer = numberProvider.GetComposer(); INumberContext numberContext = await numberDirector .CreateContextAsync( numberComposer, mainCard, cardType, context.CardExtensionContext is ICardStoreExtensionContext storeContext ? storeContext.Request.Info : null, transactionMode: NumberTransactionMode.WithoutTransaction);

// и вызовем метод NotifyOnCustomEvent, в котором расположена бизнес-логика // взаимодействия с номерами await numberDirector.NotifyOnEventAsync( numberContext, NumberEventTypes.CustomAction, async (x, ct) => true);

context.ValidationResult.Add(numberContext.ValidationResult); }

return StageHandlerResult.CompleteResult; } } }

Тип этапа вместе с его дескриптором регистрируется на сервере Tessa.Extensions.Server.

using Tessa.Extensions.Default.Server.Workflow.KrProcess.Workflow; using Tessa.Extensions.Shared.Numbers; using Tessa.Platform; using Unity; using Unity.Lifetime;

namespace Tessa.Extensions.Server.Numbers { [Registrator] public sealed class Registrator : RegistratorBase { public override void RegisterUnity() { this.UnityContainer .RegisterType<MyStageTypeHandler>(new ContainerControlledLifetimeManager()) ; }

public override void FinalizeRegistration() { this.UnityContainer .TryResolve<IKrProcessContainer>() ? .RegisterHandler<MyStageTypeHandler>(MyStageTypeDescriptors.MyStageDescriptor) ; } } }

Таким образом, реализуется любая бизнес-логика с номерами, в т.ч. с добавленными в проекте типами номеров NumberType, которые могут размещаться в произвольных полях карточки INumberLocation.

Вызвать метод с такой бизнес-логикой возможно при наличии ссылок на объекты: IUnityContainer, тип карточки CardType и экземпляр загруженной карточки Card. Т.е. вызов возможен практически из любого скрипта или расширения в системе.

Back to top