NAV4U (онлайн-журнал) » Blog Archive » Cost Adjustment (Коррекция Себестоимости), Web Service и Workflow Foundation. Моделирование и реализация Workflow под NAV 2009 SP1

Cost Adjustment (Коррекция Себестоимости), Web Service и Workflow Foundation. Моделирование и реализация Workflow под NAV 2009 SP1

В предыдущей статье данного номера мы говорили о такой достаточно удобной вещи, как автоматическая коррекция себестоимости, осуществляемая посредством запуска Windows Service, которая, обращаясь к Web Service, вызывает процедуру коррекции в Навижине. После окончания работы коррекции себестоимости, служба Windows автоматически останавливается. Помимо прочего, параллельно записывается лог системного журнала. При желании, можно прикрутить шедулер и служба будет запускаться в любое удобное для Вас время.
В данной статье мне хотелось бы попробовать применить такую замечательную технологию как WWF (Windows Workflow Foundation). Почти как «Всемирный фонд дикой природы» (World Wide Fund for Nature)…

Описывать детально теорию большого желания нет, ибо в Паутине есть все, а ничего нового я не напишу. Так что, всем тем, кто не знаком с базовыми принципами Windows Workflow Foundation, рекомендую обратиться к соответствующим источникам информации. Тем более, что описываемый пример будет достаточно примитивен и доступен для понимания всем желающим.
Итак, вкратце.


Windows Workflow Foundation (далее WF) – технология, предоставляемая компанией Microsoft. Основное ее предназначение заключается в удобстве определения, управления и выполнения рабочих процессов. WF в той реализации, которую предоставляет Microsoft, отчасти, доступна даже людям, далеким от программирования, поскольку ориентирована на визуальную разработку с использованием встроенного визуального отладчика и дизайнера процессов.

WF, как и другие технологии подобного рода, очень плотно связан с понятиями SOAP, WS, WCF и т.д.
Для реализации примера я использовал Visual Studio 2008 в качестве среды разработки, C# в качестве языка реализации и .NET Framework 3.5 (версии 3.0 будет более чем достаточно). По-умолчанию предполагаю, что все остальные Frameworks, необходимые для корректной работы с Web Services (далее WS), установлены.

Начнем. Будем запускать пакетное задание по коррекции стоимостных операций с возможностью фильтрации по товарам и товарным категориям. К слову, такая возможность есть в базовом Report 795 «Adjust Cost - Item Entries».

За основу будет взят мой Codeunit, описанный в предыдущей статье. Чуть позже мы слегка модернизируем его.

Для начала создадим новый Workflow-проект, выбрав указанный шаблон (Sequential Workflow Library)

Новый WorkFlow проект 

В результате, на Ваших экранах должен появиться Workflow-дизайнер, который, по-умолчанию, имеет точку входа и точку выхода:

Дизайнер WorkFlow

Как нетрудно догадаться, бизнес-процесс будет исполнен только в том случае, когда схема будет содержать хотя бы один «блок активности». Более того, обратите внимание, что мы выбрали Sequential Workflow шаблон. Это наиболее примитивная форма Workflow, позволяющая выстраивать и управлять несложными бизнес-процессами без тяжелых ветвлений, циклов, переходов на предыдущие стадии и т.д. По сути, это даже и не совсем Workflow в широком смысле этого слова.

Прежде чем приступить к описанию и реализации Workflow, подготовим «почву» для этого со стороны Нава.

В первую очередь, создадим новый Codeunit 50003 «Adjust Cost - Item Entries 2». За основу возьмем Codeunit 50002 «Adjust Cost - Item Entries», созданный нами в предыдущей статье.

Изменим функцию AdjstCostItemEntries. Сделаем так, чтобы она принимала 2 параметра типа Text длиной 250 символов и возвращала так же текст. Ниже приведен код данной функции:

Функция AdjstCostItemEntries

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

Вторая функция данного кодэюнита представлена ниже:

Функция CalcItemsQuantity

Смысл ее будет пояснен чуть позже. Хотя по представленному в ней коду все становится предельно ясно.

Не забыв зарегистрировать данный Codeunit в качестве веб-сервиса, добавим WinForm-приложение в качестве второго проекта:

 Новый Windows Form Application проект

В конечном счете, нам необходим такой вид формы:

Вид Windows Forms приложения

Как нетрудно догадаться, нажатие кнопки «Adjust Cost» будет инициировать процесс расчета коррекции себестоимости, запуская Workflow, который, в свою очередь, будет обращаться к WS NAV. Но для того, чтобы WinForm-приложение могло передать параметры (код товара или код товарной категории) в Workflow, необходимо разместить на схеме 2 блока – WebServiceInput и WebServiceOutput. Находятся они, как и все остальные блоки, в Toolbox-панели:

Добавление блоков WebServiceInput и WebServiceOutput

С WebServiceInput-блоком необходимо связать интерфейс, через который WinForm-клиент будет «общаться» с Workflow. Добавим интерфейс IItemInitialization.cs в Workflow-проект:

public interface IItemInitialization 
{ 
  string itemInitialization(string itemNo, string itemCategory); 
}

После чего инициализируем свойства WebServiceInput-блока так, чтобы они выглядели следующим образом:

Свойства webServiceInputActivity1

Как видим по красным меткам, необходимо связать параметры интерфейсного метода со свойствами, которые будут «присущи» Workflow и с которыми мы будем в дальнейшем работать. Данные свойства в дальнейшем будут инициализированы сразу же после обращения WinForm-клиента.

Чтобы связать свойство с параметром itemCategory, необходимо нажать на кнопку в окне свойств справа от данного параметра и выбрать вкладку «Bind to a new member». Вот что должно у Вас в итоге получиться:

Bind to an activity’s property

После этого, Visual Studio сама пропишет необходимый для обработки данных свойств код.
Напоследок же, свяжем WebServiceOutput-блока c WebServiceInput-блоком:

Свойства webServiceOutputActivity1

На текущий момент мы имеем простейшую схему, не представляющую с практической стороны ничего интересного:

Схема Sequential Forkflow

Теперь после того, как мы инициализировали указанные блоки, необходимо опубликовать Workflow в качестве веб-сервиса, иначе WinForm-приложение не сможет его «увидеть» и, как следствие, не сможет «достучаться» до Навижина:

Публикация Workflow в качестве веб-службы

После того, как мы опубликовали Workflow в качестве веб-сервиса, опишем обращение к нему из WinForm-клиента, не забыв предварительно добавить Reference на наш Workflow-проект и стандартные .NET-сборки для работы с Workflow:

Solution Explorer - My Workflow

В качестве MyProxy выступает Workflow, публикуемый как веб-сервис. Чтобы обратиться к нему, используем следующий код:

 
using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Windows.Forms; 
using System.Workflow.Activities; 
using System.Workflow.Activities.Rules; 
using System.Workflow.ComponentModel; 
using MyWorkflow; 
using System.Net;                
 
namespace Client 
{ 
  public partial class Form1 : Form 
  { 
    public Form1() 
    { 
      InitializeComponent(); 
    }                
 
    private void button1_Click(object sender, EventArgs e) 
    { 
      this.myResult.Text = "Cost Adjustment is processing..."; 
      this.myResult.Update();                
 
      MyProxy.Workflow1_WebService wf = new Client.MyProxy.Workflow1_WebService();                
 
      this.myResult.Text = wf.itemInitialization(this.itemNoFilter.Text, this.itemCategoryFilter.Text); 
    }                
 
    private void itemNoFilter_TextChanged(object sender, EventArgs e) 
    { 
      this.itemCategoryFilter.Enabled = this.itemNoFilter.Text == ""; 
    }                
 
    private void itemCategoryFilter_TextChanged(object sender, EventArgs e) 
    { 
      this.itemNoFilter.Enabled = this.itemCategoryFilter.Text == ""; 
    } 
  } 
}

Как видим, по факту, WinForm-приложение минимально задействовано в работе. Его основное предназначение – только передать параметры в Workflow. Всю остальную работу Workflow сделает сам. Вся схема Workflow приводится ниже:

Полная схема Workflow

А вот тот код, который содержит Workflow:

 
using System; 
using System.ComponentModel; 
using System.ComponentModel.Design; 
using System.Collections; 
using System.Drawing; 
using System.Linq; 
using System.Workflow.ComponentModel.Compiler; 
using System.Workflow.ComponentModel.Serialization; 
using System.Workflow.ComponentModel; 
using System.Workflow.ComponentModel.Design; 
using System.Workflow.Runtime; 
using System.Workflow.Activities; 
using System.Workflow.Activities.Rules; 
using System.Net;                
 
namespace MyWorkflow 
{ 
  public sealed partial class Workflow1 : SequentialWorkflowActivity 
  { 
    private AdjustCost.AdjustCostItemEntries2 NAVAdjustment;                
 
    public Workflow1() 
    { 
      InitializeComponent(); 
    }                
 
    public static DependencyProperty itemNoProperty = 
      DependencyProperty.Register("itemNo", typeof(System.String), typeof(MyWorkflow.Workflow1));                
 
    [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)] 
    [BrowsableAttribute(true)] 
    [CategoryAttribute("Parameters")] 
    public String itemNo 
    { 
      get 
      { 
        return ((string)(base.GetValue(MyWorkflow.Workflow1.itemNoProperty))); 
      } 
      set 
      { 
        base.SetValue(MyWorkflow.Workflow1.itemNoProperty, value); 
      } 
    }                
 
    public static DependencyProperty itemCategoryProperty = 
      DependencyProperty.Register("itemCategory", typeof(System.String), typeof(MyWorkflow.Workflow1));                
 
    [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)] 
    [BrowsableAttribute(true)] 
    [CategoryAttribute("Parameters")] 
    public String itemCategory 
    { 
      get 
      { 
        return ((string)(base.GetValue(MyWorkflow.Workflow1.itemCategoryProperty))); 
      } 
      set 
      { 
        base.SetValue(MyWorkflow.Workflow1.itemCategoryProperty, value); 
      } 
    }                
 
    private void callNavision(object sender, EventArgs e) 
    { 
      NAVAdjustment = new AdjustCost.AdjustCostItemEntries2(); 
      NAVAdjustment.Credentials = new NetworkCredential("My_WindowsLogin", "My_Password"); 
      theResult = NAVAdjustment.CalcItemsQuantity(itemNo, itemCategory).ToString(); 
    }                
 
    private void codeActivity2_ExecuteCode(object sender, EventArgs e) 
    { 
      theResult = "There are no items with filters you have applied..."; 
    }                
 
    private void codeActivity3_ExecuteCode(object sender, EventArgs e) 
    { 
      theResult = NAVAdjustment.AdjstCostItemEntries(itemNo, itemCategory); 
    }                
 
    public static DependencyProperty theResultProperty = 
      DependencyProperty.Register("theResult", typeof(System.String), typeof(MyWorkflow.Workflow1));                
 
    [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)] 
    [BrowsableAttribute(true)] 
    [CategoryAttribute("Parameters")] 
    public String theResult 
    { 
      get 
      { 
        return ((string)(base.GetValue(MyWorkflow.Workflow1.theResultProperty))); 
      } 
      set 
      { 
        base.SetValue(MyWorkflow.Workflow1.theResultProperty, value); 
      } 
    } 
  } 
}

Попробуем разобрать данный код, коррелируя его на блок-схему:

Блок Назначение Свойства
InputActivity1 Инициализация входящих параметров через интерфейс IItemInitialization. Properties InputActivity1
codeActivity1 Вызов функции CallNavision Properties codeActivity1
codeActivity2 Если функция NAV’a CalcItemsQuantity, вызываемая из CallNavision, возвращает значение 0, то происходит выполнение функции codeActivity2_ExecuteCode. CalcItemsQuantityProperties codeActivity2
codeActivity3 Если функция NAV’a CalcItemsQuantity, вызываемая из CallNavision, возвращает значение, не равное 0, то происходит выполнение функции codeActivity3_ExecuteCode, которая, непосредственно, вызывает процедуру коррекции в NAV.Если фильтр по товарам/товарным категориям установлен, то в процессе коррекции этот факт будет учтен. CalcItemsQuantity не равно нулюProperties codeActivity3
OutputActivity1 Возвращает значение свойства theResult в WinForms приложение для отображения результата коррекции в статусной строке

Запустим WinForm-клиента без фильтров по товарам: и товарным категориям:

 Adjust Cost - Item Entries (Windows Forms)

Запустим WinForm-клиента с некорректным фильтром по несуществующей товарной категории:

Некорректный фильтр по товарной категории

Запустим WinForm-клиента по товару 1000 (небезызвестный всем нам велосипед):

Коррекция себестоимости товара 1000

На этом все. Если будут пожелания, вопросы, комментарии – буду рад услышать. Файлами так же могу поделиться, если будут желающие лицезреть студийный Solution вживую.

Метки: ,



Оставьте свой отзыв!