Пример создания workflow-процесса в Dynamics 365

Данная статья содержит пример создания workflow-процесса в Dynamics 365 for Finance and Operations. Статья описывает workflow-процесс и его поэтапное создание в среде Visual Studio.

Что такое workflow-процесс?

Термин workflow-процесс можно определить 2 способами: как система и как бизнес-процесс.

Документооборот как система:

Workflow-процесс — это система, которая устанавливается с Microsoft Dynamics и выполняется на сервере Application Object Server (AOS). Система workflow-процессов предоставляет функции, позволяющие создавать отдельные workflow-процессы, или бизнес-процессы.

Документооборот как бизнес-процесс:

Workflow-процесс представляет бизнес-процесс. Он определяет потоки, или перемещения, документа по системе, показывая, кто должен выполнить задачу, принять решение или утвердить документ.

Этапы создания

Перечисление

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

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

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

Таблица

В таблице документов необходимо переопределить метод canSubmitToWorkflow(). Этот метод реализует логику, чтобы определить, является ли запись действительной для отправки в рабочий процесс. Для этого примера запись будет действительной, если она имеет статус Черновик.

            
public boolean canSubmitToWorkflow(str _workflowType = '')
{
    boolean ret = super(_workflowType);
    
    ret = this.DocumentStatus == WFDocumentStatus::Draft;
    
    return ret;
}

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

            
public static void updateWorkflowStatus(RecId _documentRecId, WFDocumentStatus _status)
{
    ttsbegin;
    
    WFDocumentTable document;
    
    update_recordset document
        setting DocumentStatus = _status
        where document.RecId == _documentRecId;
    
    ttscommit;
}

Запрос

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

Примечание: нужно установить для свойства «Динамические поля» значение «Да».

Категория процесса

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

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

Тип процесса

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

  • Category: категория рабочего процесса из предыдущего шага.
  • Query: запрос, созданный для таблицы документов.
  • Document menu item: элемент меню для формы таблицы документа.

После завершения работы мастера, Visual Studio создает объекты в проекте.

Тип рабочего процесса, который создает мастер, имеет несколько свойств. Большинство из них являются свойствами, которые указывают, что рабочий процесс должен вызывать для определенных событий. Единственное, что нужно изменить это ярлык и текст справки на что-то более значимое. Другие объекты, которые создает мастер:

  • Класс Document: этот класс реализует метод, который возвращает ссылку на запрос.
  • Класс EventHandler: этот класс обрабатывает специфичные для типа события. Мастер создает методы, к которым можно добавить бизнес-логику.
  • Класс SubmitManager: рабочий процесс вызывает этот класс при отправке документа в рабочий процесс.
  • Элемент меню действия CancelMenuItem: рабочий процесс использует этот элемент меню для отмены рабочего процесса в документе.
  • Элемент меню действия SubmitMenuItem: рабочий процесс использует этот элемент меню для отправки документа в рабочий процесс. Этот пункт меню вызывает класс SubmitManager.

Диспетчер отправки

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

            
public class WFDocumentWFTypeSubmitManager 
{
    private WFDocumentTable document;
    private WorkflowVersionTable versionTable;
    private WorkflowComment comment;
    private WorkflowWorkItemTable workItem;
    private SysUserId userId;
    private boolean isSubmission;
    private WorkflowTypeName workflowType;

    /// 
    /// Main method
    /// 
    /// calling arguments
    public static void main(Args _args)
    {
        if (_args.record().TableId != tableNum(WFDocumentTable))
        {
            throw error('Error attempting to submit document');
        }

        WFDocumentTable document = _args.record();
        FormRun caller = _args.caller() as FormRun;
        boolean isSubmission = _args.parmEnum();
        MenuItemName menuItem = _args.menuItemName();

        WFDocumentWFTypeSubmitManager manager = WFDocumentWFTypeSubmitManager::construct();
        manager.init(document, isSubmission, caller.getActiveWorkflowConfiguration(), caller.getActiveWorkflowWorkItem());

        if (manager.openSubmitDialog(menuItem))
        {
            manager.performSubmit(menuItem);
        }

        caller.updateWorkflowControls();
    }

    /// 
    /// Construct method
    /// 
    /// new instance of submission manager
    public static WFDocumentWFTypeSubmitManager construct()
    {
        return new WFDocumentWFTypeSubmitManager();
    }

    /// 
    /// parameter method for document
    /// 
    /// new document value
    /// current document
    public WFDocumentTable parmDocument(WFDocumentTable _document = document)
    {
        document = _document;

        return document;
    }

    /// 
    /// parameter method for version
    /// 
    /// new version table value
    /// current version table
    public WorkflowVersionTable parmVersionTable(WorkflowVersionTable _versionTable = versionTable)
    {
        versionTable = _versionTable;

        return versionTable;
    }

    /// 
    /// parameter method for comment
    /// 
    /// new comment value
    /// current comment value
    public WorkflowComment parmComment(WorkflowComment _comment = comment)
    {
        comment = _comment;

        return comment;
    }

    /// 
    /// parameter method for work item
    /// 
    /// new work item value
    /// current work item value
    public WorkflowWorkItemTable parmWorkItem(WorkflowWorkItemTable _workItem = workItem)
    {
        workItem = _workItem;

        return workItem;
    }

    /// 
    /// parameter method for user
    /// 
    /// new user value
    /// current user value
    public SysUserId parmUserId(SysUserId _userId = userId)
    {
        userId = _userId;

        return userId;
    }

    /// 
    /// parameter method for isSubmission flag
    /// 
    /// flag value
    /// current flag value
    public boolean parmIsSubmission(boolean _isSubmission = isSubmission)
    {
        isSubmission = _isSubmission;

        return isSubmission;
    }

    /// 
    /// parameter method for workflow type
    /// 
    /// new workflow type value
    /// current workflow type
    public WorkflowTypeName parmWorkflowType(WorkflowTypeName _workflowType = workflowType)
    {
        workflowType = _workflowType;

        return workflowType;
    }

    /// 
    /// Opens the submit dialog and returns result
    /// 
    /// true if dialog closed okay
    protected boolean openSubmitDialog(MenuItemName _menuItemName)
    {
        if (isSubmission)
        {
            return this.openSubmitDialogSubmit();
        }
        else
        {
            return this.openSubmitDialogResubmit(_menuItemName);
        }
    }

    /// 
    /// Open submission dialog
    /// 
    /// true if dialog closed okay
    private boolean openSubmitDialogSubmit()
    {
        WorkflowSubmitDialog submitDialog = WorkflowSubmitDialog::construct(this.parmVersionTable());
        submitDialog.run();
        this.parmComment(submitDialog.parmWorkflowComment());
        
        return submitDialog.parmIsClosedOK();
    }

    /// 
    /// Open resubmit dialog
    /// 
    /// true if dialog closed okay
    private boolean openSubmitDialogResubmit(MenuItemName _menuItemName)
    {
        WorkflowWorkItemActionDialog actionDialog = WorkflowWorkItemActionDialog::construct(workItem, WorkflowWorkItemActionType::Resubmit, new MenuFunction(_menuItemName, MenuItemType::Action));
        actionDialog.run();
        this.parmComment(actionDialog.parmWorkflowComment());
        this.parmUserId(actionDialog.parmTargetUser());

        return actionDialog.parmIsClosedOK();
    }

    /// 
    /// initializes manager
    /// 
    /// document
    /// calling menu item
    /// workflow version
    /// workflow item
    protected void init(WFDocumentTable _document, boolean _isSubmission, WorkflowVersionTable _versionTable, WorkflowWorkitemTable _workItem)
    {
        this.parmDocument(_document);
        this.parmIsSubmission(_isSubmission);
        this.parmVersionTable(_versionTable);
        this.parmWorkItem(_workItem);
        this.parmWorkflowType(this.parmVersionTable().WorkflowTable().TemplateName);
    }

    /// 
    /// perform workflow submission
    /// 
    protected void performSubmit(MenuItemName _menuItemName)
    {
        if (isSubmission)
        {
            this.performSubmitSubmit();
        }
        else
        {
            this.performSubmitResubmit(_menuItemName);
        }
    }

    /// 
    /// perform workflow submit
    /// 
    private void performSubmitSubmit()
    {
        if (this.parmWorkflowType() && WFDocumentTable::findRecId(document.RecId).DocumentStatus == WFDocumentStatus::Draft)
        {
            Workflow::activateFromWorkflowType(workflowType, document.RecId, comment, NoYes::No);
            WFDocumentTable::updateWorkflowStatus(document.RecId, WFDocumentStatus::Submitted);
        }
    }

    /// 
    /// perform workflow resubmit
    /// 
    private void performSubmitResubmit(MenuItemName _menuItemName)
    {
        if (this.parmWorkItem())
        {
            WorkflowWorkItemActionManager::dispatchWorkItemAction(workItem, comment, userId, WorkflowWorkItemActionType::Resubmit, _menuItemName);
            WFDocumentTable::updateWorkflowStatus(document.RecId, WFDocumentStatus::Submitted);
        }
    }

}

При использовании этого класса следует помнить, что пункт меню действия отправки должен иметь значение NoYes::Yes в параметре Enum value. Аналогично, любые пункты меню действий повторной отправки должны иметь значение NoYes::No в параметре Enum value.

События типа рабочего процесса

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

            
public void started(WorkflowEventArgs _workflowEventArgs)
{
    RecId documentRecId = _workflowEventArgs.parmWorkflowContext().parmRecId();
    WFDocumentTable::updateWorkflowStatus(documentRecId, WFDocumentStatus::Submitted);
}

public void canceled(WorkflowEventArgs _workflowEventArgs)
{
    RecId documentRecId = _workflowEventArgs.parmWorkflowContext().parmRecId();
    WFDocumentTable::updateWorkflowStatus(documentRecId, WFDocumentStatus::Rejected);
}

public void completed(WorkflowEventArgs _workflowEventArgs)
{
    RecId documentRecId = _workflowEventArgs.parmWorkflowContext().parmRecId();
    WFDocumentTable::updateWorkflowStatus(documentRecId, WFDocumentStatus::Approved);
}

Одобрение рабочего процесса

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

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

  • Workflow document: выберите класс Document, созданный мастером типа рабочего процесса.
  • Document preview field group: выберите группу полей для отображения идентифицирующей информации таблицы документа.
  • Document menu item: выберите пункт меню для формы таблицы документов.

Как и мастер типа рабочего процесса, этот мастер создает много объектов в проекте.

Список созданных объектов:

  • Класс EventHandler: этот класс обрабатывает события, связанные с одобрением. Мастер создает методы, к которым можно добавить бизнес-логику.
  • Класс ResubmitActionMgr: рабочий процесс вызывает этот класс, когда пользователь повторно отправляет документ.
  • Пункт меню Approve: рабочий процесс использует этот пункт меню для подтверждения одобрения.
  • Элемент меню действия DelegateMenuItem: рабочий процесс использует этот элемент меню при делегировании одобрения.
  • Элемент меню Reject: рабочий процесс использует этот пункт меню при отклонении одобрения.
  • Элемент меню действия RequestChange: рабочий процесс использует этот элемент меню при запросе изменения.

События подтверждения рабочего процесса

В этом примере добавляется логика в два метода обработки событий.

            
public void canceled(WorkflowElementEventArgs _workflowElementEventArgs)
{
    RecId documentRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();
    WFDocumentTable::updateWorkflowStatus(documentRecId, WFDocumentStatus::Rejected);
}

public void completed(WorkflowElementEventArgs _workflowElementEventArgs)
{
    RecId documentRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();
    WFDocumentTable::updateWorkflowStatus(documentRecId, WFDocumentStatus::Rejected);
}

Добавление подтверждения к типу

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

Включить рабочий процесс в форме

Последний шаг в создании настраиваемого рабочего процесса — включить его в форме. В узле сведений формы таблицы документа изменяются свойства Workflow Data Source, Workflow Enabled и Workflow Type.

Создание нового рабочего процесса из настроенного рабочего процесса

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

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

Примечание: приложение-редактор рабочих процессов поддерживает только браузер Internet Explorer.

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

Примечания

  1. Custom workflows in Microsoft Dynamics 365 for Operations

Comments

So empty here ... leave a comment!

Добавить комментарий

Sidebar