Sales Document Preview

As promised, it is time to share an enhancement for the document posting preview.

The posting preview functionality in NAV 2016 is built around a single-instance Codeunit number 19.  That Codeunit has temporary tables declared as global variables and what ever is written to these tables remains there even if the actual database write is rollbacked.

The standard functionality displays a navigation page where the user can view the entries about to be posted.

Preview

So far so good.

Still, my customers have never asked for this feature.  They have, however, asked for the possibility to preview the document report.  That is what this blog post is about.

PreviewDocument

There are a few steps we need to follow in order to get there.  I will go through these steps.  If you want to cheat than just download the attached objects for NAV 2016 W1 CU2 and merge the changes into your database.

We will use the same method, that is, saving the document header and document lines in temporary tables and using them to feed the reports.  We will start by creating a copy of our sales invoice (206) and sales credit memo reports (207).  We need a new reports to handle temporary tables and we also want to make a few changes to make sure that it is clearly a preview report.

The changes we make are;

  • Change the Text Constant “Sales – Invoice %1” to “Sales – Invoice Preview”
  • In the Request Page, remove options for NoOfCopies and LogInteraction
  • Change DataItem “Sales Invoice Header” and “Sales Invoice Line” to be temporary
  • Clear ReqFilterHeadingML and ReqFilterFields properties for DataItem “Sales Invoice Header”
  • Add a public function “SetProperties” with parameters for the temporary tables and use the COPY function to copy the temporary table instance to the report Data Items.

SetProperties

Make the same changes to the credit memo report.

Now, lets look at Codeunit 19 and add what ever we need there.  It should be obvious that we need to add the global variables for our sales document tables.  We also add a boolean variable to change the mode from navigation to sales document preview.

Codeunti19Globals.PNG

Make sure that these temporary tables are emptied in initialization.

DeleteAllTempTables.PNG

Use the new EventSubscriber functionality to gather the document header and lines.

EventSubscribers

Externally we should be able to change the preview mode to a document preview mode.

SetDocumentPreviewMode.PNG

The last function we need is the one running the preview report.  We define our two new reports as a local variable and call them from this function.

RunReportPreview.PNG

The final step for Codeunit 19 is to make sure that the correct action is taken depending on our new boolean variable.

ShowAllEntries.PNG

We also need to make changes to Codeunit 81.  From there the Preview is started.  We need to make a change to the standard code to enable the document preview.  The rollback must be performed before calling the ShowAllEntries.  Rollback is done by using ASSERTERROR ERROR(”);

ErrorMoved.PNG

Almost done.  Another public function to Codeunit 81 is needed.  Here we change the preview mode before starting the default preview functionality.

StartPreviewDocument.PNG

Now, we can start adding actions to the sales pages.  For example in page 9305, Sales Order List.

  • Copy action 25
  • Rename to “Preview Document”
  • Change Image to PrintDocument
  • Add Codeunit 81 as a local variable in the action C/AL code
  • Add C/AL code SalesPostYesNo.PreviewDocument(Rec);

Pressing that new action will bring up this request page.

RequestPage.PNG

Pressing Preview should give you a good preview of how the printed document will look like after posting.

PreviewSalesInvoice.PNG

Download DocumentPreview and start playing.

Compilation error for Reports

In a brand new installation of Dynamics NAV 2016 CU2 I am getting errors when compiling reports.

AppCrash

After selecting to close the program I get this error

CrashDetails

Details in the code section below.

Problem signature:
Problem Event Name: APPCRASH
Application Name: vbc.exe
Application Version: 14.0.1055.0
Application Timestamp: 563c1d26
Fault Module Name: s\SYSTEM32\MSVCP120_CLR0400.dll!__crtGetFileInformationByHandle
Fault Module Version: 6.3.9600.18146
Fault Module Timestamp: 5650afd4
Exception Code: c0000139
Exception Offset: 0009d572
OS Version: 6.3.9600.2.0.0.16.7
Locale ID: 1039
Additional Information 1: 1abe
Additional Information 2: 1abee00edb3fc1158f9ad6f44f0f6be8
Additional Information 3: 1abe
Additional Information 4: 1abee00edb3fc1158f9ad6f44f0f6be8

---------------------------
Microsoft Dynamics NAV Development Environment
---------------------------
Error while validating RDL content:
An unexpected error occurred while compiling expressions. Native compiler return value: ‘-1073741511’.

I did some digging and ended in a page describing a known issue for security updates 3098779 and 3097997 for the .NET Framework 4.5.1 and 4.5.2 after you install the .NET Framework 4.6 on Windows 8.1, Windows RT 8.1, and Windows Server 2012 R2.

After applying resolution to scenario 1 I am good to go.

NAV 2016 Data Exchange – file import

As promised it is time to pick more things that I have improved for NAV 2016.  Still on the Data Exchange, and looking at file import.  We can import a text file; fixed and delimited, with different encoding.

By using Codeunit 1240 for the External Data Handling action we are able to get any text file into the Data Exchange framework.  It will simply prompt the user for a local file name.

Microsoft shipped Codeunit 1241 to read a flat file.  I decided to change that a bit with two enhancements.  First, I wanted to be able to read a delimited file and second, I wanted to be able to use different encoding.

The Codeunit name is “Fixed File Import” and just the name change show the difference.  My version is named “Fixed/Delimited File Import”.

There are settings in the Data Exchange that I wanted to be able to support.

FileDetails

The standard Codeunit only reads the MSDOS File Encoding with this code

OnRun(VAR Rec : Record "Data Exch.")
"File Content".CREATEINSTREAM(ReadStream);
DataExchDef.GET("Data Exch. Def Code");
LineNo := 1;
REPEAT
ReadLen := ReadStream.READTEXT(ReadText);
IF ReadLen > 0 THEN
ParseLine(ReadText,Rec,LineNo,SkippedLineNo);
UNTIL ReadLen = 0;

Changed

WITH DataExchDef DO BEGIN
GET("Data Exch. Def Code");
CASE "File Encoding" OF
"File Encoding"::"MS-DOS" :
"File Content".CREATEINSTREAM(ReadStream,TEXTENCODING::MSDos);
"File Encoding"::WINDOWS :
"File Content".CREATEINSTREAM(ReadStream,TEXTENCODING::Windows);
"File Encoding"::"UTF-8" :
"File Content".CREATEINSTREAM(ReadStream,TEXTENCODING::UTF8);
"File Encoding"::"UTF-16" :
"File Content".CREATEINSTREAM(ReadStream,TEXTENCODING::UTF16);
END;
LineNo := 1;
REPEAT
ReadLen := ReadStream.READTEXT(ReadText);
IF ReadLen > 0 THEN
ParseLine(ReadText,Rec,LineNo,SkippedLineNo);
UNTIL ReadLen = 0;
END;

to read the four different text encoding that are supported by the Data Exchange Setup.

The PharseLine section in the standard Codeunit has this simple loop to read the fixed file.

StartPosition := 1;
REPEAT
DataExchField.InsertRecXMLField(DataExch."Entry No.",LineNo,DataExchColumnDef."Column No.",'',
COPYSTR(Line,StartPosition,DataExchColumnDef.Length),DataExchLineDef.Code);
StartPosition += DataExchColumnDef.Length;
UNTIL DataExchColumnDef.NEXT = 0;
LineNo += 1;

Extending this and adding two functions gives me the ability to import both fixed and variable text file.

StartPosition := 1;
REPEAT
CASE DataExchDef."File Type" OF
DataExchDef."File Type"::"Fixed Text" :
BEGIN
DataExchField.InsertRecXMLField(DataExch."Entry No.",LineNo,DataExchColumnDef."Column No.",'',
COPYSTR(Line,StartPosition,DataExchColumnDef.Length),DataExchLineDef.Code);
StartPosition += DataExchColumnDef.Length;
END;
DataExchDef."File Type"::"Variable Text" :
BEGIN
DataExchField.InsertRecXMLField(DataExch."Entry No.",LineNo,DataExchColumnDef."Column No.",'',
ExtractFirstDelimitedPart(Line,GetSeparator),DataExchLineDef.Code);
END;
ELSE
ERROR(FileTypeNotSupported,DataExchDef."File Type");
END;
UNTIL DataExchColumnDef.NEXT = 0;
LineNo += 1;

LOCAL GetSeparator() Separator : Text[1]
WITH DataExchDef DO
CASE "Column Separator" OF
"Column Separator"::Tab :
Separator[1] := 9;
"Column Separator"::Semicolon :
Separator := ';';
"Column Separator"::Comma :
Separator := ',';
"Column Separator"::Space :
Separator := ' ';
END;

LOCAL ExtractFirstDelimitedPart(VAR Line : Text;Separator : Text[1]) FirstPart : Text
SeparatorPosition := STRPOS(Line,Separator);
IF SeparatorPosition > 0 THEN BEGIN
FirstPart := COPYSTR(Line,1,SeparatorPosition - 1);
IF SeparatorPosition + 1 <= STRLEN(Line) THEN
Line := COPYSTR(Line,SeparatorPosition + 1)
ELSE
Line := '';
END ELSE BEGIN
FirstPart := Line;
Line := '';
END;

More to come.  Stay tuned…