Service to import from Serial Port

Similar to the last post I have also been asked to listen to a serial port and import the data into NAV.  I even used the same VB.NET service.  On the NAV side I added two functions to my web service.

Where RMSerial is a table with the following code.

Here is the VB.NET code for the service.

[code lang=”vb”]Imports System
Imports System.Timers
Imports System.Net
Imports System.IO
Imports System.IO.Ports

Public Class FileImportService
Dim Salvor1 As Salvor.SalvorWebService
Dim Timer2 As System.Timers.Timer
Dim User As New System.Net.NetworkCredential
Dim Serial1 As New System.IO.Ports.SerialPort

Protected Overrides Sub OnStart(ByVal args() As String)
‘ Add code here to start your service. This method should set things
‘ in motion so your service can do its work.
Salvor1 = New Salvor.SalvorWebService
User.Domain = "<Domain>"
User.UserName = "<User>"
User.Password = "<Password>"
Salvor1.Credentials = User

Timer2 = New System.Timers.Timer(30000)
AddHandler Timer2.Elapsed, AddressOf ProcessSerialData
Timer2.Interval = 30000
Timer2.Enabled = True
Timer2.Stop()

If My.Settings.COMPort <> "" Then
If Serial1.IsOpen Then
Serial1.Close()
End If
WriteDebug("COM Port Selected: " & My.Settings.COMPort)
Serial1.PortName = My.Settings.COMPort
Serial1.BaudRate = 9600
Serial1.Parity = Parity.Even
Serial1.DataBits = 8
Serial1.StopBits = 1
Serial1.Open()
If Serial1.IsOpen Then
WriteDebug("Serial Port Opened")
Else
WriteDebug("Failed to open Serial Port")
End If
AddHandler Serial1.DataReceived, AddressOf Serial1_DataReceived
End If
End Sub

Protected Overrides Sub OnStop()
‘ Add code here to perform any tear-down necessary to stop your service.
If My.Settings.COMPort <> "" Then
If Serial1.IsOpen Then
Serial1.Close()
End If
End If
End Sub

Protected Sub Serial1_DataReceived()
Timer2.Stop()
WriteDebug("Serial Event Started, buffer size: " & Serial1.ReadBufferSize)
Dim LineRead As String
Dim LineResponse As String = ""
Dim WaitLoop As Integer = 0
LineRead = Serial1.ReadExisting
If LineRead = "!" Then
LineResponse = "$"
ElseIf LineRead = "*" Then
LineResponse = "&"
ElseIf Left(LineRead, 1) = "[" Then
While Right(LineRead, 1) <> "]" And WaitLoop < 10
Threading.Thread.Sleep(100)
LineRead = LineRead & Serial1.ReadExisting
WaitLoop = WaitLoop + 1
End While
If Right(LineRead, 1) = "]" Then
Try
If Salvor1.AboutCompany = "SAM" Then
LineResponse = Salvor1.COMPort(LineRead)
End If
Catch ex As Exception
LineResponse = "%"
WriteToEventLog("Web Service unavailable:" & ex.ToString, EventLogEntryType.Error)
End Try
Else
LineResponse = "%"
End If
End If
Serial1.Write(LineResponse)
WriteDebug("Serial read: " & LineRead)
WriteDebug("Serial response: " & LineResponse)
Timer2.Interval = 30000
Timer2.Enabled = True
Timer2.Start()
End Sub

Protected Sub ProcessSerialData()
Timer2.Enabled = False
Timer2.Stop()
Dim Success As Boolean
Try
Success = Salvor1.ProcessSerialData
If Not Success Then
WriteToEventLog("Failed to process serial data", EventLogEntryType.Error)
End If
Catch ex As Exception
WriteToEventLog("Web Service unavailable:" & ex.ToString, EventLogEntryType.Error)
End Try
End Sub

Protected Sub WriteToEventLog(ByVal Message As String, ByVal EntryType As EventLogEntryType)
Dim MyLog As New EventLog()
‘ Check if the the Event Log Exists
If Not Diagnostics.EventLog.SourceExists(Me.ServiceName) Then
Diagnostics.EventLog.CreateEventSource(Me.ServiceName, Me.ServiceName & " Log")
‘ Create Log
End If
MyLog.Source = Me.ServiceName
‘ Write to the Log
Diagnostics.EventLog.WriteEntry(MyLog.Source, Message, EntryType)
End Sub

Protected Sub WriteDebug(ByVal Message As String)
If My.Settings.Debug Then
WriteToEventLog(Message, EventLogEntryType.Information)
End If
End Sub
End Class
[/code]

Service to import files

The task is to import every file that is dropped into a specific folder on my local drive into NAV.  The solution is a windows service programmed in Visual Studio 2008 VB.NET.

The first step is to create a web service in Dynamics NAV that accepts a text line and a file name.  Another function to remove the file if the import fails and the third to process the file after it has been imported.

The vb.net code from Visual Studio

[code lang=”vb”]Imports System
Imports System.Timers
Imports System.Net
Imports System.IO

Public Class FileImportService
Dim Salvor1 As Salvor.SalvorWebService
Dim Timer1 As System.Timers.Timer
Dim User As New System.Net.NetworkCredential

Protected Overrides Sub OnStart(ByVal args() As String)
‘ Add code here to start your service. This method should set things
‘ in motion so your service can do its work.
Salvor1 = New Salvor.SalvorWebService
User.Domain = "<Domain>"
User.UserName = "<User>"
User.Password = "<Password>"
Salvor1.Credentials = User

Timer1 = New System.Timers.Timer(30000)
AddHandler Timer1.Elapsed, AddressOf OnTimedEvent

Timer1.Interval = 30000
Timer1.Enabled = True
Timer1.Start()
WriteDebug("Timer 1 Started")

‘ If the timer is declared in a long-running method, use
‘ KeepAlive to prevent garbage collection from occurring
‘ before the method ends.
GC.KeepAlive(Timer1)

End Sub

Protected Overrides Sub OnStop()
‘ Add code here to perform any tear-down necessary to stop your service.
End Sub

Protected Sub OnTimedEvent(ByVal source As Object, ByVal e As ElapsedEventArgs)
Timer1.Enabled = False
Timer1.Stop()
WriteDebug("File Event Started")
Try
If Salvor1.AboutCompany = "SAM" Then
ReadFolder()
End If
Catch ex As Exception
WriteToEventLog("Web Service unavailable:" & ex.ToString, EventLogEntryType.Error)
End Try
Timer1.Enabled = True
Timer1.Start()
End Sub

Protected Sub ReadFolder()
Dim dirInfo As New DirectoryInfo(My.Settings.ImportFolder)
Dim FileArray As FileInfo() = dirInfo.GetFiles()

For Each TextFile In FileArray
If ReadFile(TextFile) Then
WriteDebug("Check File: " & TextFile.Name)
If Salvor1.ProcessFile(TextFile.Name) Then
DeleteFile(TextFile)
Else
WriteDebug("Rollback File: " & TextFile.Name)
Salvor1.RemoveFile(TextFile.Name)
End If

Else
Salvor1.RemoveFile(TextFile.Name)
End If
Next

End Sub

Protected Function ReadFile(ByVal TextFile As FileInfo) As Boolean
Dim Success As Boolean
Try
If File.Exists(TextFile.FullName) Then
Dim ioFile As New StreamReader(TextFile.FullName)
Dim ioLine As String
Success = True

While Not ioFile.EndOfStream
ioLine = ioFile.ReadLine
Success = Success And Salvor1.InsertLine(TextFile.Name, ioLine)
End While
ioFile.Close()
End If
Catch ex As Exception
Success = False
WriteToEventLog("Import of file " & TextFile.FullName & " failed:" & ex.ToString, EventLogEntryType.Error)
End Try
Return Success
End Function

Protected Sub DeleteFile(ByVal TextFile As FileInfo)
Try
WriteDebug("Delete File: " & TextFile.Name)
TextFile.Delete()
Catch ex As Exception
WriteToEventLog("Failed to delete file " & TextFile.FullName & ":" & ex.ToString, EventLogEntryType.Error)
End Try
End Sub

Protected Sub WriteToEventLog(ByVal Message As String, ByVal EntryType As EventLogEntryType)
Dim MyLog As New EventLog()
‘ Check if the the Event Log Exists
If Not Diagnostics.EventLog.SourceExists(Me.ServiceName) Then
Diagnostics.EventLog.CreateEventSource(Me.ServiceName, Me.ServiceName & " Log")
‘ Create Log
End If
MyLog.Source = Me.ServiceName
‘ Write to the Log
Diagnostics.EventLog.WriteEntry(MyLog.Source, Message, EntryType)
End Sub

Protected Sub WriteDebug(ByVal Message As String)
If My.Settings.Debug Then
WriteToEventLog(Message, EventLogEntryType.Information)
End If
End Sub
End Class[/code]

A DotNet Interop Soap Web Request

I am currently working on a solution that requires a Dynamics NAV client to communicate with Dynamics NAV web service.  This I have done before with the classic client and have used automation objects for the job.  Now I wanted to do this with dotnet only objects in the Role Tailored Client.  Took some time to put all things together but here it is.  This version is running the request from the client.

 

Prepare for Report Transformation

On of the bigger tasks when upgrading to the Role Tailored Client is the report transformation.  In big database there are houndreds of reports.  Some of them might just be idle reports that are never used.  This blog from ArcherPoint that is based on a original post from Mark Brummel got me thinking.  In 2009 Microsoft added to the client the possibility to execute a function with ID 120 in codeunit 1 that only works for the Classic Client.

I mixed these together by adding the code to codeunit 1 in the same way as Mark Brummel but creating a report log table instead of a report print count table.

Here the Entry No. field automatically increments the numbers in the database.  I also changed the property DataPerCompany for the table to No.

The single instance codeunit is also a simple one.

The next step might just be to create a report for Excel Pivot table like I did with ledger tables.

Report Printing Log

Send multiple base 64 encoded files to a single Web Service

Sometimes a little more flexibility is needed than a single XML Port in a web service function.  Then it is possible to send multiple files to a single web service.  I am working on an enhancement for the inter-company posting feature in Dynamics NAV.

To add to the standard functionality I want to be able to use web services to deliver an invoice from one company to another.  I send two XML files and two PDF files to web service.

In the Classic Client I use then ‘CG Request Client’.Base64 to encode the files into a XML node.

[code]
XMLElement3.appendChild(XMLNode);
XMLNode := XMLRequestDoc.createElement(‘pDFInvoice’);
IF "PDF Document".HASVALUE THEN BEGIN
PDFInvoiceFileName := FileMgt.ClientTempFileName(”,’pdf’);
ICOutboxTrans."PDF Document".EXPORT(PDFInvoiceFileName,FALSE);
Base64.Encode(PDFInvoiceFileName,XMLNode);
ERASE(PDFInvoiceFileName);
END;
XMLElement3.appendChild(XMLNode);[/code]

in the Role Tailored Client I use dotnet interop

[code]
XMLNode4 := XMLRequestDoc.CreateElement(‘pDFInvoice’);
IF "PDF Document".HASVALUE THEN BEGIN
"PDF Document".CREATEINSTREAM(InStr);
DOWNLOADFROMSTREAM(InStr,Text006,Path,Text009,PDFInvoiceFileName);
XMLNode4.InnerText := Convert.ToBase64String(ClientFile.ReadAllBytes(PDFInvoiceFileName));
ClientFile.Delete(PDFInvoiceFileName);
END;
XMLElement3.AppendChild(XMLNode4);[/code]

On the service part it is possible to write the file to a BLOB without using a temporary file.

[code]
IF PDFInvoice.LENGTH > 0 THEN BEGIN
Bytes := Convert.FromBase64String(PDFInvoice);
MemoryStream := MemoryStream.MemoryStream(Bytes);
ICInboxTransaction."PDF Document".CREATEOUTSTREAM(OutStr);
MemoryStream.WriteTo(OutStr);
END;[/code]

A message from Dan Brown, General Manager Dynamics NAV

This is a message to you all from Dan Brown General Manager, Dynamics NAV:

Hi, everyone.

Over the past several months the NAV team has worked hard getting ready to release Microsoft Dynamics NAV 2013. We’ve run hundreds of thousands of performance-, stress-, unit- and regression-tests daily. We’ve monitored the comments you’ve made on our first-ever public Beta of NAV and incorporated the feedback as much as possible. And, we’ve worked with partners bringing several customers live on NAV 2013. All of this has been to ensure that the product is of the highest possible quality before we ship. We’re looking forward to getting the RTM version in your hands as soon as possible!

Microsoft Dynamics NAV 2013 is probably the biggest launch of the product ever. It concludes the transition from the classic client/server 2-tier proprietary architecture developed in the 1990’s to a state-of-the-art, 4-tier Microsoft architecture capable of rendering multiple clients and facilitating multiple modes of integration. It also means that Microsoft Dynamics NAV now is a full-blown member of the Microsoft server family and adheres to all Microsoft standards in terms of security, reliability and scalability. Finally, it signals the beginning of a new era of “NAV in the cloud,” opening up an array of new opportunities using NAV and integrating it with Microsoft and non-Microsoft products.

With Microsoft Dynamics NAV 2013 coming out this fall, the NAV TechDays conference is a great opportunity for everybody in the NAV developer community to learn more about and get ready for the release. All the sessions at NAV TechDays are technical and long enough to allow the speakers to go into enough detail for the developers in the audience to understand what the features in the product are about and how to use them. Since NAV TechDays is a conference for developers by developers with deep technical content, we are sending some of our best developers who designed and wrote the code to attend and speak at the conference. If you have questions about your favorite feature, you will have an opportunity to give your feedback on Microsoft Dynamics NAV 2013 and provide input on what you would like to see in future releases.

I hope to see you all at NAV TechDays in Antwerp, Belgium on September 27th.

-Dan

____________________________________
Daniel C. Brown
General Manager, Dynamics NAV

Accessing your non english data from the MS SQL server

If you are like me, located in a non-English speaking country and would like users to be able to use other tools then the Dynamics NAV clients to access the company data you will find that all the meta data in the database is in English.  This means that you will have to translate the fields and sometimes the data to your language.

The problems are:

  • Field captions are unavailable in the MS SQL database
  • Option values are shown as number
  • Boolean is shown as number
  • Global dimension do not have the correct caption
  • Time is shown as DateTime
  • Not easy to see the difference between normal date and closing date

The solution that I am using is to create a separate database on the same database server and create localized views in that database.  What you get with the solution is:

  • A date table that can be used to show all properties of a given date
  • A import of the option value captions
  • A selection of tables to make accessible
  • A batch job to create the localized SQL view for each table

Here is a list of the fields in the date table

Field No. Field Name Data Type Length
1 Date Date
2 Date Name Text 30
3 Year Integer
4 Week Integer
5 Month Integer
6 Month Name Text 20
7 Day of Week Integer
8 Day Name Text 20
9 Day of Month Integer
10 Closing Date Boolean
11 SQL Month Name Text 20
12 SQL Day Name Text 20
13 Quarter Integer
14 Year Month Integer
15 Year Month Name Text 20
16 Month Year  Name Text 20
17 Quarter Name Text 20
18 VAT Period Integer
19 VAT Period Name Text 20
20 Sorting Date Integer
21 HRMS Integer Start Integer
22 HRMS Integer End Integer
23 Day of Year Integer
24 Day of Half Year Integer
25 Day of Quarter Integer
26 Day of Accounting Integer
27 Half Years Integer
28 Half Year of Year Integer
29 Is Holiday Boolean
30 Is Working Day Boolean
31 Month of Half Year Integer
32 Month of Quarter Integer
33 Month of Year Integer
34 Quarters of Half Year Integer
35 Quarters of Year Integer
36 Week of Year Integer
37 Is Week Day Boolean
41 Half Year Name Text 20
42 Week Name Text 20
102 Fiscal Day Date
103 Fiscal Year Integer
104 Fiscal Week Integer
105 Fiscal Month Integer
107 Fiscal Day of Week Integer
109 Fiscal Day of Month Integer
113 Fiscal Quarter Integer
123 Fiscal Day of Year Integer
124 Fiscal Day of Half Year Integer
125 Fiscal Day of Quarter Integer
127 Fiscal Half Years Integer
128 Fiscal Half Year of Year Integer
131 Fiscal Month of Half Year Integer
132 Fiscal Month of Quarter Integer
133 Fiscal Month of Year Integer
134 Fiscal Quarters of Half Year Integer
135 Fiscal Quarters of Year Integer
136 Fiscal Week of Half Year Integer
137 Fiscal Week of Month Integer
138 Fiscal Week of Quarter Integer
139 Fiscal Week of Year Integer
140 Fiscal Quarter Name Text 20
141 Fiscal Half Year Name Text 20
142 Fiscal Week Name Text 20
143 Fiscal Month Name Text 20

Lets take the G/L Entry table as an example. In the MS SQL the field names are in English and the data not readable for the normal user.

and for example the [Document Type] will show as numbers.  Here is an example of the output of my tool to create a localized view for this table.

Executing this will give me a view in my database that I can use to fetch localized data from the G/L Entry table with all the above problems solved.

By using the additional date table as dimension in OLAP or as join in a SQL query I can easily find all aspects of the “Posting Date” in the G/L Entry table and group entries accordingly.