Impossible to debug the Job Queue in NAV 2016?

I guess I am not the only one that has installed NAV 2016 running the Job Queue.  If the Job Queue is running a job that causes error you are likely to start the debugger on the service tier running the Job Queue and start with a Debug Next.

DebugNext2

You can expect a break point with an error similar to this one.

Error

Well, not what you where hoping for.  Not the job you where planning to debug, right.  Well, you try again and get the same error, never getting to the error you wanted to debug in the first place.  And why is that?

Microsoft decided to utilize the TryFunction in the Job Queue

TryFunction

So, each time JobQueueEntry.FINDFIRST fails the debugger breaks.  So I asked Microsoft, why change this from being an normal function containing EXIT(JobQueueEntry.FINDFIRST).  The response was:

“We wrapped the FINDFIRST in a TRY-function because we regularly got (dead-)lock errors when we had multiple queues running at the same time, and this fix has virtually eliminated that problem. And since we cannot have return values from try-functions, we would have to pass on a boolean VAR parameter to indicate if it was found or not.”

Ok, so there is a reason, and a good one.

So I started a little ping pong with Microsoft and we agreed that this should be fixed.  A new local function was added to Codeunit 448 and the TryFunction was modified.  The previous call to a TryFunction was modified to call the new local function instead.

448Modification

So, both problems fixed; (dead-)lock not causing the Job Queue to stop and we are not getting troublesome break points in the debugger.

Attached are Delta, FOB and TXT versions for this fix.

COD448

 

 

Arion Banki currency importer

It is not often that I post a solution that is intended to be used in Iceland only.  Here is one.

Arion Banki is one of the major national banks in Iceland and they want to support Dynamics NAV users.  On their website you will find the currency exchange rates for every working date.  I created an importer that will download the exchange rate from their website and import into NAV.

There are two ways to do this.  First is to go into Currencies in the NAV Client and click on Import

ArionBankiImportCurrency

The other way is to add a Job Queue Entry to make this an automatic task.

NewJobQueueEntry

The process will find the last date imported into your system and import all days from and including that date to the current working date.

CurrencyImportNAV2009 CurrencyImporterObjects2013R2

 

 

Use SAVEASPDF to E-Mail Invoices and Credit Memos

When I was running NAV 2009 I used PDFCreator, BioPDF or BullZipPDF printers to create a PDF copy of a report.  In NAV 2013 we have the option to use native SAVEASPDF command that uses built-in methods to create a PDF document on the server.

I used the Job Queue in NAV 2009 to send all my invoices and credit memos via email to my customers.  An updated version for NAV 2013 that uses

[code]
FileName := FileMgt.GetDirectoryName(FileMgt.ServerTempFileName(‘pdf’)) + ‘\’ + STRSUBSTNO(‘%1 %2.pdf’,TABLECAPTION,"No.");
IF EXISTS(FileName) THEN
ERASE(FileName);
SalesInvHeader := "Sales Invoice Header";
SalesInvHeader.SETRECFILTER;
CLEAR(Invoice);
Invoice.SETTABLEVIEW(SalesInvHeader);
Invoice.USEREQUESTPAGE(FALSE);
IF Invoice.SAVEASPDF(FileName) THEN BEGIN[/code]

is attached below.

JobQueueEMailer2013

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

Enable Inventory Adjustment from Job Queue

Today I posted a new feedback to Microsoft.  My experience is that companies that use inventory should execute Inventory Adjustment with the Job Queue every night.  To be able to do this a change is required to the OpenWindow trigger in codeunit 5895.

The old code is

[code]OpenWindow()
Window.OPEN(
Text000 +
‘#1########################\\’ +
Text001 +
Text003 +
Text004 +
Text005 +
Text006);
WindowIsOpen := TRUE;

UpDateWindow(NewWindowAdjmtLevel : Integer;NewWindowItem : Code[20];NewWindowAdjust : Text[20];NewWindowFWLevel : Integer;NewWindowEntr
WindowAdjmtLevel := NewWindowAdjmtLevel;
WindowItem := NewWindowItem;
WindowAdjust := NewWindowAdjust;
WindowFWLevel := NewWindowFWLevel;
WindowEntry := NewWindowEntry;

IF IsTimeForUpdate THEN BEGIN
IF NOT WindowIsOpen THEN
OpenWindow;
Window.UPDATE(1,STRSUBSTNO(Text002,AdjmtBuf.FIELDCAPTION("Item No."),WindowItem));
Window.UPDATE(2,WindowAdjmtLevel);
Window.UPDATE(3,WindowAdjust);
Window.UPDATE(4,WindowFWLevel);
Window.UPDATE(5,WindowEntry);
END;[/code]

and the new code is

[code]
OpenWindow()
IF GUIALLOWED THEN BEGIN
Window.OPEN(
Text000 +
‘#1########################\\’ +
Text001 +
Text003 +
Text004 +
Text005 +
Text006);
WindowIsOpen := TRUE;
END;
END;

UpDateWindow(NewWindowAdjmtLevel : Integer;NewWindowItem : Code[20];NewWindowAdjust : Text[20];NewWindowFWLevel : Integer;NewWindowEntr
WindowAdjmtLevel := NewWindowAdjmtLevel;
WindowItem := NewWindowItem;
WindowAdjust := NewWindowAdjust;
WindowFWLevel := NewWindowFWLevel;
WindowEntry := NewWindowEntry;
IF GUIALLOWED THEN BEGIN
IF IsTimeForUpdate THEN BEGIN
IF NOT WindowIsOpen THEN
OpenWindow;
Window.UPDATE(1,STRSUBSTNO(Text002,AdjmtBuf.FIELDCAPTION("Item No."),WindowItem));
Window.UPDATE(2,WindowAdjmtLevel);
Window.UPDATE(3,WindowAdjust);
Window.UPDATE(4,WindowFWLevel);
Window.UPDATE(5,WindowEntry);
END;
END;[/code]

If you agree with this please vote here.  A simple extension to the Job Queue can be found in an earlier post.

Job Queue stops when lock time out occurs

Running NAS with Job Queue will start a timer to process Job Queue Entries every two seconds. In the original code the Timer is disabled before checking the Job Queue Entries and then enabled again after the process. If NAS will not be able to read the Job Queue Entry then the function will exit without enabling the Timer and nothing will be processed.

The original code in Codeunit 448 is
[code htmlscript=”false”]HandleRequest()
JobQueueSetup.GET;
IF NOT JobQueueSetup."Job Queue Active" THEN
EXIT;

NavTimer.Enabled := FALSE;

ThisSessionIsActive := UpdateJobQueueSession(JobQueueEntry,FALSE);

CleanUpJobQueue;
COMMIT;

NavTimer.Enabled := TRUE;[/code]
I suggest that you will find a suitable period of time where everything in the Queue should be processed and change the behaviour. Do not disable the timer, just change the interval.  Here I change the interval to five minutes.

The replacement code would then be
[code htmlscript=”false”]HandleRequest()
JobQueueSetup.GET;
IF NOT JobQueueSetup."Job Queue Active" THEN
EXIT;

NavTimer.Enabled := FALSE;
NavTimer.Interval := 5 * 60 * 1000; // 5 min
NavTimer.Enabled := TRUE;

ThisSessionIsActive := UpdateJobQueueSession(JobQueueEntry,FALSE);

CleanUpJobQueue;
COMMIT;

NavTimer.Enabled := FALSE;
NavTimer.Interval := 2 * 1000; // 2 sec.
NavTimer.Enabled := TRUE;[/code] 

Dialog in Dynamics NAV

In so many cases when writing code for Dynamics NAV you want to display a dialog to notify the user or open a progress dialog.  Today, we always need to consider that the code might be running from a web service where the GUIALLOWED variable is set to false.

I created a codeunit to replace the dialog variable type in my code.  That codeunit is attached at the end.  This saves me work and the code is quite simple.
[code htmlscript=”false”]Customer – OnPreDataItem()
DialogInstance.WindowOpen(
‘Reading Customer #1##################\\’ +
‘@2@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@’);
DialogInstance.WindowSetTotal(2,COUNT);

Customer – OnAfterGetRecord()
DialogInstance.WindowProcess(2);
DialogInstance.WindowUpdateText(1,"No.");

Customer – OnPostDataItem()
DialogInstance.WindowClose;[/code]
The Dialog Instance codeunit checks if a dialog can be opened and only updates the progress dialog ten times per second.
[code htmlscript=”false”]WindowProcess(WindowIndex : Integer)
IF NOT GUIALLOWED THEN
EXIT;

Counter[WindowIndex] := Counter[WindowIndex] + 1;

IF NOT DialogIsOpen THEN
EXIT;

IF (CURRENTDATETIME – WindowLastUpdated) > 100 THEN BEGIN
Window.UPDATE(WindowIndex,ROUND(Counter[WindowIndex] / Total[WindowIndex] * 10000,1));
WindowLastUpdated := CURRENTDATETIME;
END;[/code]
DialogInstance

Using Web Services for your NAS jobs

In NAV 7 the NAV Application Server will no longer be supported.  The Job Queue has been redesigned to support the new STARTSESSION feature that will create a new session on the service tier to execute a given task.

In NAV 2009 and going forward it is possible to use web services to act like an application server with the help of a simple program with a timer.

For example a program with a code like this
[code htmlscript=”false” lang=”vb”]Public Class NAVAppServer
Dim Success As Boolean
Dim NAVApp1 As New NAVApp.NAVAppServer
Dim SystemUser As New System.Net.NetworkCredential
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)
NAVApp1.UseDefaultCredentials = True
NAVApp1.Url = "https://dynamics.is:7047/DynamicsNAS/WS/Dynamics%20Inc/Codeunit/NAVAppServer"
NAVApp1.ExecuteCodeunit(50059, True)

End Sub
End Class[/code]
The Codeunit NAVAppServer is here and also attached
[code htmlscript=”false”]ExecuteCodeunit(CodeunitID : Integer;Log : Boolean) Success : Boolean
LogEntryNo := InsertLogEntry(5,CodeunitID);
Success := CODEUNIT.RUN(CodeunitID);
UpdateLogEntry(LogEntryNo,Success);

InsertLogEntry(ObjectType : ‘,,,Report,,Codeunit’;ObjectNo : Integer) : Integer
WITH JobQueueLogEntry DO BEGIN
INIT;
ID := CREATEGUID;
"User ID" := USERID;
"Start Date/Time" := CURRENTDATETIME;
"Object Type to Run" := ObjectType;
"Object ID to Run" := ObjectNo;
INSERT(TRUE);
COMMIT;
EXIT("Entry No.");
END;

UpdateLogEntry(LogEntryNo : Integer;WasSuccess : Boolean)
WITH JobQueueLogEntry DO BEGIN
GET(LogEntryNo);
"End Date/Time" := CURRENTDATETIME;
IF WasSuccess THEN
Status := Status::Success
ELSE BEGIN
Status := Status::Error;
SetErrorMessage(COPYSTR(GETLASTERRORTEXT,1,1000));
END;
MODIFY;
COMMIT;
END;[/code]
This is published as a web service by adding an entry into table 2000000076 “Web Service”.

NAVAppServerCodeunit

 

Batch Job to Quit if it is blocking

I have a batch job that is running regularly to adjust items.  Since this is running during working hours I want to stop the batch if it is blocking another user. This is the loop that my batch does.
[code htmlscript=”false”]FOR i := 1 TO 50 DO BEGIN
IF AmILocking(LockingForUserID) THEN BEGIN
COMMIT;
ERROR(Text001,LockingForUserID);
END;
AdjustItems;
END;[/code]
Text001 is “Locking for user id %1, quitting !” and AmILocking function is
[code htmlscript=”false”]AmILocking(VAR LockingForUserID : Text[30]) Locking : Boolean
Session.SETRANGE(Blocked,TRUE);
Locking := Session.FINDFIRST;
LockingForUserID := Session."User ID";[/code]
where Session is the virtual table Session as a local variable.

E-Mailing PDF Report with Job Queue

One of my clients asked for a automated report delivery every morning.  Since Job Queue was already running I decided to create a way to have Job Queue print the report to PDF and email it as an attachment.

This example uses the E-Mailer Report no. 50003 to send Report No. 6 to my email address.

First I had to make some changes to codeunit 449, Job Queue Start Codeunit, the OnRun trigger.  I created a local variable with the name JobQueueEntry as a Record 472 and then added to the code.
[code htmlscript=”false”]CASE "Object Type to Run" OF
"Object Type to Run"::Codeunit:
CODEUNIT.RUN("Object ID to Run",Rec);
"Object Type to Run"::Report:
//#01
IF "Parameter String" <> ” THEN BEGIN
JobQueueEntry := Rec;
JobQueueEntry.SETRECFILTER;
REPORT.RUN("Object ID to Run",FALSE,FALSE,JobQueueEntry);
END ELSE
//#01
REPORT.RUN("Object ID to Run",FALSE);
END;[/code]
Attached is report 50003 that does the job.

Any Report E-Mailer