AutoFormatType and Role Tailored Layout

If you have worked on a Role Tailored Layout and wanted to show a report without decimals you have most likely seen that the AutoFormatType option that we have in the Dynamics NAV 2009 R2 Classic Client does not work in the Role Tailored Layout

If you study the dataset that the Role Tailored Client delivers to the report the numbering format is not related to the AutoFormatType.  Why Microsoft chose to ignore this in this release I will never know.

In Iceland we do not use decimals in currency amounts.  However, when printing currency amount they are supposed to show decimals.  So I created my own AutoFormat solution.

The first step is to create a codeunit that uses the same logic as the AutoFormatType and delivers the Role Tailored Layout number format.

[code]OBJECT Codeunit 50012 RTC Decimal Format
{
OBJECT-PROPERTIES
{
Date=29.11.12;
Time=09:47:37;
Version List=Dynamics.is;
}
PROPERTIES
{
OnRun=BEGIN
END;

}
CODE
{
VAR
GLSetup@1100408003 : Record 98;
Currency@1100408004 : Record 4;
SetupRead@1100408000 : Boolean;

PROCEDURE AutoFormatLayout@12(AutoFormatType@1000 : Integer;AutoFormatExpr@1001 : Text[80]) : Text[80];
VAR
AmountDecimalPlaces@1100408000 : Text[30];
BEGIN
IF AutoFormatType = 0 THEN
EXIT(”);

IF NOT GetSetup THEN
EXIT(”);

CASE AutoFormatType OF
1: // Amount
IF AutoFormatExpr = ” THEN
EXIT(CreateNumberFormat(GLSetup."Amount Decimal Places"))
ELSE BEGIN
IF GetCurrency(COPYSTR(AutoFormatExpr,1,10)) AND
(Currency."Amount Decimal Places" <> ”)
THEN
EXIT(CreateNumberFormat(Currency."Amount Decimal Places"))
ELSE
EXIT(CreateNumberFormat(GLSetup."Amount Decimal Places"));
END;

2: // Unit Amount
IF AutoFormatExpr = ” THEN
EXIT(CreateNumberFormat(GLSetup."Unit-Amount Decimal Places"))
ELSE BEGIN
IF GetCurrency(COPYSTR(AutoFormatExpr,1,10)) AND
(Currency."Unit-Amount Decimal Places" <> ”)
THEN
EXIT(CreateNumberFormat(Currency."Unit-Amount Decimal Places"))
ELSE
EXIT(CreateNumberFormat(GLSetup."Unit-Amount Decimal Places"));
END;
10: EXIT(”);
END;
END;

LOCAL PROCEDURE CreateNumberFormat@1100408000(AmountDecimalPlaces@1100408000 : Text[30]) RTCFormat : Text[30];
VAR
Pos@1100408001 : Integer;
DecimalPlaces@1100408002 : Integer;
BEGIN
Pos := STRPOS(AmountDecimalPlaces,’:’);
IF Pos > 0 THEN
AmountDecimalPlaces := COPYSTR(AmountDecimalPlaces,Pos + 1);

IF NOT EVALUATE(DecimalPlaces,AmountDecimalPlaces) THEN
DecimalPlaces := 0;

RTCFormat := ‘#,##0’;
IF DecimalPlaces > 0 THEN
RTCFormat := RTCFormat + ‘.’ + PADSTR(”,DecimalPlaces,’0′);
END;

LOCAL PROCEDURE GetSetup@10015() : Boolean;
BEGIN
IF SetupRead THEN EXIT(TRUE);

SetupRead := GLSetup.GET;

EXIT(SetupRead);
END;

LOCAL PROCEDURE GetCurrency@1(CurrencyCode@1000 : Code[10]) : Boolean;
BEGIN
IF CurrencyCode = Currency.Code THEN
EXIT(TRUE);
IF CurrencyCode = ” THEN BEGIN
CLEAR(Currency);
Currency.InitRoundingPrecision;
EXIT(TRUE);
END;
EXIT(Currency.GET(CurrencyCode));
END;

BEGIN
END.
}
}

[/code]

Then I implement this on the Classic Section, example in Report 105.  First I add my codeunit to Globals.

Then I insert two hidden fields in the Sections.

The first field for the LCY format

and the second field for the current currency format

This adds the fields CurrencyAutoFormat and LCYAutoFormat to my dataset.

The final step is to replace the default Format property in the Role Tailored Layout.  I look at the field CustBalanceDue_1_ and see that this field can in some cases show currency amount and sometimes it shows LCY amounts.  This is determined by the PrintAmountsInLCY value.  I then set the Format property for this field to “=iif(Fields!PrintAmountsInLCY.Value,Fields!LCYAutoFormat.Value,Fields!CurrencyAutoFormat.Value)”

Next I look at the sum for this field.  This sum is always shown in the local currency.  I then set the Format property for this field to “=First(Fields!LCYAutoFormat.Value)”.

This way I have implemented the AutoFormatType functionality to the Role Tailored Layout.

RTCAutoformat Codeunit

Using a .dll proxy for web services

I have now completed my first all-dotnet codeunit.  The codeunit uses a dll file that I created from the web service WDSL.  This makes the programming a lot easier.

This solution has a BLOB fields that stores both incoming and outgoing xml.  When I use a proxy dll I don’t build a xml document and I never handle xml documents.  Again dotnet has a solution.  I use xml serializer from the system.xml object.

[code]LOCAL PROCEDURE SerializeToXMLStream@1100408008(VAR Object@1100408002 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Object" RUNONCLIENT;VAR NavOutstr@1100408004 : OutStream);
VAR
xmlSerializer@1100408000 : DotNet "’System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Xml.Serialization.XmlSerializer" RUNONCLIENT;
StreamWriter@1100408003 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.IO.StreamWriter" RUNONCLIENT;
File@1100408001 : DotNet "’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.IO.File" RUNONCLIENT;
NavInstr@1100408005 : InStream;
BEGIN
IF ISNULL(Object) THEN EXIT;
StreamWriter := StreamWriter.StreamWriter(TempFileName);
xmlSerializer := xmlSerializer.XmlSerializer(Object.GetType);
xmlSerializer.Serialize(StreamWriter,Object);
StreamWriter.Close;
UPLOADINTOSTREAM(”,MagicPath,”,TempFileName,NavInstr);
COPYSTREAM(NavOutstr,NavInstr);
File.Delete(TempFileName);
END;[/code]

I can send the proxy object to this function and will get the xml into the BLOB field.

[code]CurrencyRates := StatementService.GetCurrencyRates(TypeRates,CREATEDATETIME(RatesDate,235959T));

WITH BankAccAction DO BEGIN
"Modified by User ID" := USERID;
"Modification Date and Time" := CURRENTDATETIME;
"Incoming Message".CREATEOUTSTREAM(OutStr);
SerializeToXMLStream(CurrencyRates,OutStr);
MODIFY;
COMMIT;
END;[/code]

This example is executed on the client since I am using certificates and web services security for the communication.

The Kill Idle batch

In an earlier post I showed how to kill idle sessions from C/AL code.  I have been asked to provide a batch that works with Job Queue.

To set this up please add a field to table 91.  Field name is “Kill Idle Duration” and the data type is “Duration”.  In my case this field has the number 50000.  If that number is unavailable just use another number and import the text object and compile.

Attached is the report that can be used in the job queue.  Just make sure that the user running the job queue has enough permissions as shown in my earlier post.

If this is of any value to you please check my copyright page and donate to the web.

Report 89209

Need NAS to communicate with a web service ?

My recent post on creating a class in visual studio to simplify the NAV C/AL code for web service is a easy solution for NAV 2013.  It will work both for the user and for the Job Queue.  In NAV 2009 it will also work for the user running the Role Tailored Client but it will not work in the Job Queue.

There is a solution.  Use the VB.NET NAV Application Server.  This application server is able to execute any codeunit in NAV with dotnet support. 

The license required to start this application server is also cheaper than the classic NAS license.

Record Links in NAV 2009 R2

I made a function that collects all the record links from the documents related to a customer ledger entry or a vendor ledger entry.  I then display the list or record links and offer the user the change to open the links or email them.

This all worked just fine in my Classic Client but in the Role Tailored Client the page gives me a metadata error.  I asked Microsoft and got an answer from Lars Lohndorf-Larsen.

I have been in contact with development and they say that this is unfortunately a restriction in NAV2009. It is not possible to use “Record Link” as Source Table on a page.
“Record Link” is a special table which gets (re)built dynamically. A page expects a normal table and will implicitly compile the underlying table. But this particular table does not have usual metadata that the page expects, so it fails.
In NAV2013 it works because this table was redesigned.

This means that I will have to use the Record Link table as a global variable instead of using it as a source table.  That is what I will do.

Use WSDL and dotnet interop in the Role Tailored Client

I have been working with the dotnet interop and consuming soap web services.  I wrote about a dotnet interop based way to communicate with soap web services in a previous blog but now I want to demonstrate another way that requires just a few lines of code.

I would like to find a web service that is open so everyone can do their own testing.  I found a web service that will look up a country by an IP address.

The first step is to start Microsoft Visual Studio and create a new project.  The language that you choose is not important.

I delete Class1.cs.  Then go to Project menu and Add Service Reference.  Click Advanced… and Add Web Reference…  Look in my previous blog for more details.

I paste the web service URL in to Visual Studio and select a name for the web reference.

I go to Project and GeoIPService Properties to make sure that I am using .NET Framework 3.5 and I choose to create my own key to sign the class.  I also like to add details to the Assembly Information…

I go to Signing and create a new key file with my own password.

Then I save the project and select Build and Build GeoIPService.  In the solution folder I will now find the GeoIPService.dll file.  This file I copy to my Classic Client Add-ins folder.  I start the Classic Client and create a new Codeunit.  I will then add a dotnet global variable with my new class.  I create a variable both for GeoIPService and for GeoIP.

I the need to choose if I would like the service to be run on the client or on the server.  If I choose to run on the client then I need to copy the class file to Role Tailored Client Add-ins folder for every user.  If I choose to run on server I copy the class file to the service Add-ins folder.  I selected to run on server since I do not have to supply my domain user name or any certificates to the service.

The code in the Codeunit is as follows

[code]
GeoIPService := GeoIPService.GeoIPService;
GeoIPService.Url := ‘http://www.webservicex.net/geoipservice.asmx’;
GeoIP := GeoIPService.GetGeoIP(‘93.95.74.198’);
MESSAGE(
‘Return Code: %1\IP Address %2\Country Name: %3\Country Code: %4’,
GeoIP.ReturnCode,
GeoIP.IP,
GeoIP.CountryName,
GeoIP.CountryCode);[/code]

I then start the codeunit in my Role Tailored Client with the Run Object Page that Microsoft supplied in their blog.

Attached is the codeunit, the class library and the Run Object Page from Microsoft.

GeoIPService

Microsoft Connect is working

On July 31, 2012 I posted a patch to codeunit 5895 to be able to execute inventory adjustment with NAS.  I also suggested that this solution should be implemented in NAV and now I am receiving a knowledge base article from Microsoft with ID: 2763255 that handles this issue.

Was Microsoft listening to my comments or did someone else suggest this to Microsoft.  At least I can see that the solution that they provide does not match exactly the solution that I suggested – but a solution anyway.