NAV4U (онлайн-журнал) » Blog Archive » Cost Adjustment (Коррекция Себестоимости), Web Service и Windows Service. Пример аналога NAS’a для NAV 2009 SP1

Cost Adjustment (Коррекция Себестоимости), Web Service и Windows Service. Пример аналога NAS’a для NAV 2009 SP1

Как известно, NAV 2009 SP1 обладает рядом свойств и качеств, которые разительно отличают новую версию от ее предшественников. К таковым плюсам, безусловно, можно отнести:

  • трехуровневую архитектуру, позволяющую, как задекларировано, выстраивать более масштабируемые и надежные решения;
  • поддержку функционирования Web Services;
  • возможность создания подключаемых компонентов самых различных уровней сложности.

Автор данной статьи не видит необходимости в больших подробностях излагать теоретические выкладки по каждому из приведенных пунктов. Почему? Во-первых, огромное море информации по Web Services можно отыскать в Паутине. Во-вторых, основам технологии создания Add-in’ов для NAV был посвящен весь предыдущий выпуск Журнала. В-третьих, хотелось бы вынести теорию в отдельную большую статью, коррелируя это на NAV. Но это все потом…

Итак!

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

Для коррекции операций стоимости в NAV по конкретным товарам и товарным категориям существует отдельное пакетное задание: Report 795 «Adjust Cost - Item Entries» - «Корр. Себест. - Товар Операции».

 Товар Корр. - Себест. Операций

Как и другие задания подобного рода, данный отчет необходимо запускать периодически, используя, к примеру, такие внешние инструментарии как Navision Application Server (NAS) или SQL Server Jobs. Причем желательно делать это в нерабочее время, поскольку количество стоимостных операций может достигать миллионов, а нагрузка на базу будет заметно возрастать в момент запуска указанного отчета. К тому же, не будем забывать и об известной проблеме блокировок таблиц…

Как было сказано выше, автоматический запуск коррекции себестоимости может осуществляться посредством использования внешних инструментов: NAS и SQL Server Jobs. Но возможности NAV 2009 позволяют нам использовать третий инструмент: сочетание работы таких технологий, как Web Services и Windows Services.

Приступим к практической реализации!

Как известно, NAV 2009 SP1 предоставляет 2 типа объектов, поддерживающих работу с Web Services: Page и Codeunit. Поэтому, поскольку коррекция себестоимости представлена в системе в качестве отчета, необходимо создать кодэюнит, реализующий основные вызовы этого стандартного отчета. Создадим такой кодэюнит в пользовательском диапазоне объектов (CU 50002 «Adjust Cost - Item Entries»).

Codeunit Adjust Cost - Item Entries

На представленной выше картинке несложно заметить отличие стандартного пакетника (слева) от вновь созданного в пользовательском диапазоне объектов кодэюнита 50002 «Adjust Cost - Item Entries» (справа). Функция «AdjstCostItemEntries» вмещает в себя основные операторы и вызовы стандартного пакетника, за исключением фильтрации по товарам и товарным категориям. Причина последнего замечания заключается в необходимости полного отсутствия любых операторов C/AL, так или иначе связанных с взаимодействием с пользователем.

Итак, кодэюнит готов, интерфейс взаимодействия прописан (таковым выступает единственная функция «AdjstCostItemEntries»). Настало время опубликовать Web Service! Как мы уже знаем, делается это через таблицу 2000000076 «Web Service» и связанный с ней Page 810 «Web Services»:

Web Services

Проверить доступность веб-сервиса очень просто: необходимо всего лишь обратиться к нему через окно браузера, не забывая при этом о важнейшей службе под названием «Microsoft Dynamics NAV Business Web Services», отвечающей за функционирование веб-сервисов под NAV. Прежде всего, убедимся в том, что служба запущена:

Windows Services

А затем проверим доступность веб-сервиса AdjustCostItemEntries:

Web Service - AdjustCostItemEntries

Теперь, когда со стороны NAV’a все готово, приступим к созданию Windows Service проекта. Для этого я использую визуальную среду разработки Visual Studio 2008, .NET Framework 3.5 и C# в качестве языка программирования:

Новый Windows Service проект

Для использования опубликованного веб-сервиса «AdjustCostItemEntries» необходимо добавить соответствующий Web Reference в только что созданный проект:

Add Web Reference

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

Solution Explorer

Здесь представлены, собственно, пространства имен и конфигурационный файл app.config (содержит информацию по веб-сервису).

Файл Service1.cs содержит, собственно, реализацию службы Windows. Помимо этого, используя данный файл, можно создать подобие инсталлятора указанной службы.

Чтобы создать обработчик инсталлятора, необходимо в файле Service1.cs вызвать из контекстного меню команду Add Installer. В результате данной манипуляции создастся файл ProjectInstaller.cs, содержимое которого должно выглядеть следующим образом:

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Configuration.Install; 
using System.Linq;          
 
namespace AdjustCostItemEntries 
{ 
  [RunInstaller(true)] 
  public partial class ProjectInstaller : Installer 
  { 
    public ProjectInstaller() 
    { 
      InitializeComponent(); 
    } 
  } 
}

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

namespace AdjustCostItemEntries 
{ 
  partial class ProjectInstaller 
  { 
    /// Required designer variable. 
    private System.ComponentModel.IContainer components = null;          
 
    /// Clean up any resources being used. 
    /// param name="disposing" true if managed resources should be disposed; otherwise, false.   
    protected override void Dispose(bool disposing) 
    { 
      if (disposing && (components != null)) 
      { 
        components.Dispose(); 
      } 
      base.Dispose(disposing); 
    }          
 
    #region Component Designer generated code          
 
    /// Required method for Designer support - do not modify 
    /// the contents of this method with the code editor. 
    private void InitializeComponent() 
    { 
      this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller(); 
      this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller(); 
      // 
      // serviceProcessInstaller1 
      // 
      this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem; 
      this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic; 
      // 
      // serviceInstaller1 
      // 
      this.serviceInstaller1.DisplayName = "Cost Adjustment"; 
      this.serviceInstaller1.ServiceName = "Service1"; 
      // 
      // ProjectInstaller 
      // 
      this.Installers.AddRange(new System.Configuration.Install.Installer[] { 
      this.serviceProcessInstaller1, 
      this.serviceInstaller1}); 
    }          
 
    #endregion          
 
    private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1; 
    private System.ServiceProcess.ServiceInstaller serviceInstaller1; 
  } 
}

Здесь ключевыми частями кода являются следующие строки:

  this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem; 
  this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic; 
  // 
  // serviceInstaller1 
  // 
  this.serviceInstaller1.DisplayName = "Cost Adjustment"; 
  this.serviceInstaller1.ServiceName = "Service1";

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

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

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Diagnostics; 
using System.Linq; 
using System.ServiceProcess; 
using System.Text; 
using System.Threading; 
using AdjustCostItemEntries.AdjstCostItemEntriesWebRef; 
using System.Net;          
 
namespace AdjustCostItemEntries 
{ 
  public partial class Service1 : ServiceBase 
  { 
    string outcome;          
 
    // таймер отвечает за контроль завершения расчета коррекции себестоимости. Мониторим каждые 5 секунд. 
    System.Timers.Timer t = new System.Timers.Timer(5000);          
 
    // контроллер работы службы (обратите внимание на параметр конструктора - это имя службы!) 
    ServiceController sc = new ServiceController("Cost Adjustment");          
 
    EventLog log;          
 
    AdjstCostItemEntriesWebRef.AdjustCostItemEntries NAVAdjustment;          
 
    public Service1() 
    { 
      InitializeComponent();          
 
      // мониторим свершившийся факт запуска службы... 
      TimerInitialization(); 
    }          
 
    void TimerInitialization() 
    { 
      t.Elapsed += new System.Timers.ElapsedEventHandler(OnTimerEvent); 
      t.Enabled = true; 
    }          
 
    void OnTimerEvent(object sender, System.Timers.ElapsedEventArgs e) 
    { 
      sc.Refresh(); 
      // если служба отработала - остановим ее, дабы не пользоваться всякими там шедулерами... :) 
      if (sc.Status.Equals(ServiceControllerStatus.Running)) 
      { 
        log.WriteEntry("Service is running. It will be stopped..."); 
        t.Stop(); 
        this.OnStop(); 
      } 
    }          
 
    protected override void OnStart(string[] args) 
    { 
      Processing(); 
    }          
 
    protected override void OnStop() 
    { 
      this.Stop(); 
    }          
 
    void Processing() 
    { 
      log = new EventLog("Application", ".", "Dynamics NAV Cost Adjustment"); 
      log.WriteEntry("Cost Adjustment processing has been started...", EventLogEntryType.Information);          
 
      NAVAdjustment = new AdjstCostItemEntriesWebRef.AdjustCostItemEntries(); 
      NAVAdjustment.UseDefaultCredentials = true; 
      NAVAdjustment.Url = "http://localhost:7047/DynamicsNAV/WS/CRONUS%20International%20Ltd/Codeunit/AdjustCostItemEntries";          
 
      try 
      { 
        log.WriteEntry("Cost Adjustment processing to be started at " + 
          DateTime.Now.TimeOfDay,EventLogEntryType.Information);          
 
        outcome = NAVAdjustment.AdjstCostItemEntries();          
 
        log.WriteEntry("Cost Adjustment processing has been done successfully at " + 
          DateTime.Now.TimeOfDay,EventLogEntryType.Information); 
      } 
      catch (Exception ex) 
      { 
        log.WriteEntry("Cost Adjustment processing was stopped! The reason: " + 
          ex.Message,EventLogEntryType.Error); 
      } 
    } 
  } 
}

Итак, что же делает данная служба? Все очень просто!

В конструкторе класса вызывается стандартная процедура InitializeComponent(), а также подписывается на специальное событие таймер. По факту свершения указанного события мы будем делать вывод – завершилась ли работа по обработке коррекции себестоимости или нет. Это будет нужно нам для остановки службы, как только она закончит выполнять свою основную работу, а именно коррекцию. Ибо нет смысла держать в фоне работающую службу, основное назначение которой – разовая работа в определенный промежуток времени (вероятнее всего – ночной).

Во время старта процедуры запускаем основную процедура Processing(). Внутри данной процедуры ведем обработку стандартного лог-файла работы приложений, в который записываем всю интересующую нас информацию.
Далее производим инициализацию веб-сервиса (прописываем стандартные настройки доступа, указываем адрес, по которому расположен веб-сервис):

 
NAVAdjustment = new AdjstCostItemEntriesWebRef.AdjustCostItemEntries(); 
  NAVAdjustment.UseDefaultCredentials = true; 
  NAVAdjustment.Url = "http://localhost:7047/DynamicsNAV/WS/CRONUS%20International%20Ltd/Codeunit/AdjustCostItemEntries";

А далее, собственно, вызываем тот единственный интерфейс, обработка которого позволяет запустить коррекцию себестоимости в Навижине. После окончания работы процесса коррекции, служба окончательно перейдет в статус «Running» (т.е., запущена). Данный факт является признаком того, что службу необходимо остановить. Теперь, когда в очередной промежуток времени (каждые 5 секунд) сработает таймер, в событии OnTimerEvent запустится следующая логика, останавливающая службу:


  log.WriteEntry("Service is running. It will be stopped...");
  t.Stop();
  this.OnStop();

На этом все, служба готова. Установим ее, используя Visual Studio 2008 Command Prompt, выполнив следующую команду, перейдя в директорию \bin\Debug, где после компиляции проекта находится .exe-файл AsjustCostItemEntries:

installutil AdjustCostItemEntries.exe

Служба установлена, и мы можем увидеть ее в консоли управления службами:

Windows Service Installed

Проверим ее работоспособность на следующем примере.

Представим, что в системе есть товар FIFOITEM, по которому есть ряд стоимостных операций:

Value Entries FIFOITEM

Запуск службы приведет к следующему закономерному результату (издержки «легли» на продажу):

Value Entries после коррекции себестоимости

К слову, теперь если Вы посмотрите на содержимое таблицы 5804 «Avg. Cost Adjmt. Entry Point», Вы увидите, что откорректированы операции стоимости по всем товарам (значения поля «Cost Is Adjusted» всех записей таблицы содержат значение TRUE):

Avg. Cost Adjmt. Entry Point

Для удаления службы из системы запустите команду installutil /u AdjustCostItemEntries.exe

Буду рад Вашим комментариям и замечаниям.

Метки: ,



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