What is ERP ?

Found a great video about the very basic, very important question:

What is ERP ?

The next question is then:

Why Dynamics NAV ? So check out a comparison between ax, gp and nav.

Advertisements

MVP Award part 2 … more or less

mvp-logo

Got mail … not from Microsoft, but from FedEx …

Bildschirmfoto_2017-07-06_04-16-53

So, what does that mean ? Do i get my 2. MVP Award ? A bit Yes and a little bit more of a No. After clarification with Cristina Herero (MVP Program Manager Europe) will MVP Awards given on Oct/16 or later – normally valid for one year, then the candidate will be reevaluated – be extended til July/2018. Veeeery strange, does not feel like a real MVP Award and it’s a bit unfair. But we have to accept that.

All of that because of the changes in the MVP Award Renew Cycle some of us new MVPs get that info in that way.

Like last time: Many, many thanks to my girlfriend for supporting me for so many years in so many ways, bringing light, love and friendliness into my life.

Many thanks to the readers of my Nav blog and the members of the Nav community.

cheers

How to: Create an automation for usage in Navision

The development of a COM component with .Net for usage in Navision needs a special approach. Start with a new C# project, project type Class Library. Set the project to “Make assembly com-visible”. The simple automation provides a string property and a method to show a .net message box.

// a sample code
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace SampleAutom
{
 // the interface, so that the methods and properties are shown in Nav
 // needed attributes InterfaceType and Guid
 [InterfaceType(ComInterfaceType.InterfaceIsDual)]
 // create a new Guid
 [Guid("9304DD04-5EF0-498E-893E-CB644CD34656")] 
 interface IMyInterface
 {
  // set a DispId for each method/property
  [DispId(1)]
  int MsgBox(string message);

  [DispId(2)]
  string Title { get; set; }
 }

 // class attributes
 [ClassInterface(ClassInterfaceType.AutoDual)]
 [Guid("D9E556F3-4D85-45C9-965A-DB3D918528CD")]
 // implement the interface
 public class TestCom : IMyInterface
 {
  string _title = "Dynamics Nav";

  public TestCom()
  { }

  // with property Title you can set/read the title of the msgbox
  public string Title
  {
    get
    { return _title; }
  
    set
    { _title = value; }
  }
  
  // method msgbox returns an integer value according the clicked button
  // Yes = 6, No = 7, Cancel = 2
  public int MsgBox(string message)
  {
    var result = MessageBox.Show(message, Title, 
       MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information);
    return (int)result;
  }
 }
}

To install the new automation run visual studio in admin mode. after restart and loading the project build/compile the project.

Now start Navision and create a new codeunit. In the OnRun trigger add following code:

// TestCom, Automation, 'SampleAutom'.TestCom 
// RetVal, Integer 
CREATE(TestCom);
TestCom.Title := '.Net Msgbox run in Navision';
RetVal := TestCom.MsgBox('Your message text ...');
MESSAGE(FORMAT(RetVal));
CLEAR(TestCom);

Now run the codeunit. It results in:

autom1    autom2

cheers

Import with Rapid Start Services

The Rapid Start Services are an often used tool in Dynamics Nav to export and import data. In that posting i show how the Import works using new feature Item Attributes.

In that sample i want to import a new Item Attribute Value and a new Item Attribute Value Mapping. Item Attribute Values are the concrete values, which an Item Attribute can have, e.g. Item Attribute “Color” can have values red, green, blue. These values can now be added as attributes to a concrete item using table item attribute value mapping.

We start with a new Config. Package. So goto /Departments/Administration/Application Setup/RapidStart Services/Configuration Packages or simply write “Configuration Packages” into the search box. There create a new Config. Package and set ITEMATT as code value and “Item Attributes” as package name. Now we add some lines to the Tables list: 7501 and 7505.

cfg4

Now select Filters in subpage menu Table.
Add a filter for the first line (table 7501). Here we want to filter for Item Attribute with ID 1 (Color): FieldID = 1,Field Filter = 1.
For the 2. line (table 7505) set following filter:

cfg5

Now export the filtered table data to excel using action “Export to Excel” from the main menu Actions. Open the created excel document. So we get the needed format to import some data. Now we change the data. Remove all data lines from table 1 in the excel document except line 1. Now change the values for ID and Value as follows:

cfg2

In the 2. table change the value for “Item Attribute Value ID”:

cfg3

Save the changed document to your documents folder, close Excel. Now we can import the new data. For that click on “Import from Excel”, No. of Package records changes to 1. If you click on the no., you can check import errors in the winow Config. Package records. The imported data is now temporarily saved. To save the data to the target tables, click on “Apply Package” action.

cfg1

Now let’s check, if the data was imported.

cfg6

Goto items list and click on action Attributes in menu Action. All right, the data is correct. You see in the second line Attribute Farbe (= Color) and the new value Brown, right the attribute value list with the new value.

cheers

Check windows client’s execution mode at runtime

If you want to check if the windows client is run in config mode at runtime, then there is an easy method to do that:

// variables
Env : DotNet : System.Environment.'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
cmdArgs : DotNet : System.Array.'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'

// code
cmdArgs := Env.GetCommandLineArgs;
IF LOWERCASE(FORMAT(cmdArgs.GetValue(1))) = '-configure' THEN
  // do something

Method GetCommandLineArgs gives back a string array. In the sample it’s expected, that the -configure parameter is the first parameter. To check all parameters you can do with that code:

FOR i := 0 TO cmdArgs.Length - 1 DO
  IF LOWERCASE(FORMAT(cmdArgs.GetValue(i))) = '-configure' THEN
    MESSAGE('Config mode');

There is an alternative method, also working, but much longer and with low performance. 😉

// variables
cmdLine : Text;
 
// code
cmdLine := LOWERCASE(GetCmdLine);
IF (STRPOS(cmdLine, '-configure') <> 0) OR (STRPOS(cmdLine, '/configure') <> 0) THEN
  Message('Config mode');

PROCEDURE GetCmdLine : Text;
// variables
FileMgt : Codeunit 419
CurrProc : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Diagnostics.Process"
MgmtObjSearcher : DotNet "'System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.System.Management.ManagementObjectSearcher"
MgmtObjColl : DotNet "'System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.System.Management.ManagementObjectCollection"
MgmtObj : DotNet "'System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.System.Management.ManagementObject"
Enum : DotNet "'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Collections.IDictionaryEnumerator"
cmdLine : Text;

// code
CurrProc := CurrProc.GetCurrentProcess;
MgmtObjSearcher := MgmtObjSearcher.ManagementObjectSearcher(
  STRSUBSTNO('SELECT CommandLine FROM Win32_Process WHERE ProcessId = %1', CurrProc.Id));

MgmtObjColl := MgmtObjSearcher.Get;
Enum := MgmtObjColl.GetEnumerator;

WHILE Enum.MoveNext DO BEGIN
  MgmtObj := Enum.Current;
  cmdLine += STRSUBSTNO(' %1', MgmtObj.Item('CommandLine'));
END;

EXIT(cmdLine);

cheers

How to work with big item descriptions

Sometimes it’s needed to save very long descriptions, but in Nav text fields can have only 250 characters. Additional you may want to search in these long text values. You can add a couple of these text fields to save long texts or save the text in text files and add them to the item. But what about searching? Not that easy.
An other option to save long texts is the usage of blob fields. For that option i developed a solution.

First add a new field “Description 3” to table Item, type BLOB, subtype Memo.
Then edit page Item Card, add a global variable Desc3Txt of type text with no length. Add the variable as new field to the item card, Editable=False, MultiLine=Yes.
Add following code to trigger OnAfterGetRecord in item card page:

// InStr | InStream
CALCFIELDS("Description 3");
IF "Description 3".HASVALUE THEN BEGIN
  "Description 3".CREATEINSTREAM(InStr);
  InStr.READ(Desc3Txt);
END;

Add to trigger Desc3Txt – OnAssistEdit()

// OutStr | OutStream 
// EditCtrl | DotNet | Archer.TextEdit.'Archer.TextEdit, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1465b259ee2284cb' 
CLEAR(EditCtrl);
EditCtrl := EditCtrl.TextEdit;
EditCtrl.Load(Desc3Txt);
EditCtrl.ShowDialog;
Desc3Txt := EditCtrl.Save;
EditCtrl.Close;
CLEAR(EditCtrl);

"Description 3".CREATEOUTSTREAM(OutStr);
OutStr.WRITE(Desc3Txt);
MODIFY;
CurrPage.UPDATE;

itse-1

Page item card with new multiline text field “Description 3” and Assist Button.

itse-2

Clicking on Assist Button starts the TextEdit Control and loads the current text. After changing the text and closing the text control, you are asked, if you want to change the text.

Now we need the opportunity to search within the new text/blob field. For that we need a new page Item Search. That new field cannot be searched using the standard search function.

Create a new page with objectid 50000, name Item Search. Add a group under the contentarea, add a global text variable SearchString, add a new field under the group with SearchString as SourceExpr. Add another group, add a new line with type part. Later we set the value for property PagePartID.

itse-3

To show the search result we need another page: type listpart, objectid 50001, name Item Search Result. As source of the new page we need a new table: objectid 50000, name Item Search Result.

itse-4

The new page 50001:

itse-5

Properties: Editable=False, SourceTable=50001, SourceTableTemporary=Yes.

Now add global function SetData(SearchFilter : Text) to the new page. Add following code to the new function:

// local variables
// Item, Record, Item 
// ItemSearchResultLine, Record, Item Search Result 
// InStr, InStream 
// Desc3Txt, Text 
// LineNo, Integer 

DELETEALL;

LineNo := 10;
Item.FINDSET;
REPEAT
  Item.CALCFIELDS("Description 3");
  IF Item."Description 3".HASVALUE THEN BEGIN
    Item."Description 3".CREATEINSTREAM(InStr);
    InStr.READTEXT(Desc3Txt);
    IF STRPOS(LOWERCASE(Desc3Txt),LOWERCASE(SearchFilter)) > 0 THEN BEGIN
      "Line No." := LineNo;
      "Item No." := Item."No.";
      Description := Item.Description;
      "Description 2" := Item."Description 2";
      "Description 3" := COPYSTR(Desc3Txt,1,250); // first 250 chars
      INSERT(FALSE);
      LineNo += 10;
    END;
  END;
UNTIL Item.NEXT = 0;

CurrPage.UPDATE(FALSE);

Now you can set the property PagePartID in the part line in page 50000 to 50001.

For calling the search function we need a Search button in page 50000.
Add following code to trigger Search – OnAction()
CurrPage.ItemSearchResultLines.PAGE.SetData(SearchString);

itse-6

The new Item Search Page with a search result.

You can download the TextEdit Control here.

Followup:
You could simplify the solution by:
* Search page: Use only page 50001, add a second group at the top with field SearchString. So page 50000 is not needed.
* Page Item Card: remove the textedit control and the according code, set field Desc3Txt to editable, add the code to fill the blob field “description 3” using outstream to trigger Desc3Txt-OnValidate.

cheers

 

Error when opening Whse. Shipment page

Got following error after creating a Whse. shipment from a sales order in Nav 2017 when opening the Whse. Shipment from page “Whse. Shipment Lines”:

The record that you tried to open is not available. 
The page will close or show the next record.

The first strange thing is, the message is an info box, not an error message. After analysing the data in the sales order and tables Whse. Shipment Header and Whse. Shipment Line no error found. So what’s up? Also debugging did not really help, the debugger did not jump to the line, where the info box is created.

In the end i found out, that the reason for the strange behaviour was following: I created a new location for some tests, but forgot to add my user to the whse. employees, but that was not shown. The causal reason in the code: When opening page 7335 Whse. Shipment from page “Whse. Shipment Lines” following code is called in OnOpenPage trigger:

ErrorIfUserIsNotWhseEmployee() // from table 7320 Whse. Shipment Header
IF USERID = '' THEN BEGIN
  WhseEmployee.SETRANGE("User ID",USERID);
  IF WhseEmployee.ISEMPTY THEN
   ERROR(Text002,USERID);
END;

Nice code, but what happens, if WhseEmployee.ISEMPTY is not empty? No error message is shown, but the info message from above with senseless text, which has nothing to do with the original error. Here it’s not checked, if there is a whse. employee created for the current user and the given location of the sales order. Thx microsoft.

What’s needed to correct the behaviour? Change above code to:

IF USERID = '' THEN BEGIN
  WhseEmployee.SETRANGE("User ID",USERID);
  WhseEmployee.SETRANGE("Location Code","Location Code");
  IF WhseEmployee.ISEMPTY THEN
    ERROR(Text002,USERID);
  END;

You should also change Text002 to “You must first set up user %1 as a warehouse employee for location %2.”.

cheers