Global Changecompany – Startsession

A wish as old as Navision: A blanket client change via program code. And unfortunately it has remained a wish until today... or has it?

Changecompany – Problem with triggers

A typical task in Navision is, for example, the import of data from a single file into different clients. For example, stocks are read in from a WMS (warehouse management system), which are assigned to different clients in the ERP.

Also, if you want to replace a mainframe application of the 70s/80s, e.g. Trampolin or Quattro Pro on the Siemens Nixdorf, then you/your application may not even know the term "client".
Basically, exactly this reading is very simple. Mainframe replacement not so much... The receiving table is either set as DataPerCompany = NO, and so all records can be read in directly in one go,
or the receiving table is left on the default DataPerCompany = , and the client is switched for each data record to be read in with ImportTable.changecompany(target client of the data record to be read in).

Neither of these is technically challenging.

However, it will be difficult to a) bring this data correctly into the ArticleBookLeaf rows of the respective target client and b) to update this table correctly.

Why? Everyone who has worked with ChangeCompany has stumbled across it.

Let's do it with this simple example:

CLEAR(item); //Important: must be placed BEFORE ChangeCompany! Clear resets ChangeCompany, in contrast to Init.
item.CHANGECOMPANY(client2)
Item.INSERT(true); //The TRUE is the evil one!

Presumably, this code will work exactly the same way.
But what is happening?
The article is created in client2 , but with an article number from the current client!
Why? Let's take a look at the InsertTrigger of ItemTable 27 in the overall context:

CLEAR(item); 
item.CHANGECOMPANY(client2)
Item.INSERT(true); ->
  IF "No." = '' THEN BEGIN
    GetInvtSetup;
    InvtSetup.TESTFIELD("Item Nos."); //InvtSetup comes from client1!
    NoSeriesMgt.InitSeries(InvtSetup."Item Nos.",xRec."No. Series",0D,"No.","No. Series"); //The number series code unit runs with all records in it in client 1!
    "Costing Method" := InvtSetup."Default Costing Method"; //InvtSetup comes from client1!
  END;

There is no way to change this behavior. Anyone working with ChangeCompany for the first time will almost inevitably stumble across this behavior. The normal expectation (in my opinion) is that the table "knows" that it works in client2, and therefore all program logic in it automatically and implicitly works with records from client2.
Mhm... Spoiler alert: It does not. The active global client applies to the session, not the object. So for the current user context.

Another funny example is this one:

Customer.get('customer number');
Customer.CHANGECOMPANY(client2);
Customer.calcfields(Balance);

IF the customer with the "customer number" exists in client 2, calcfields(Balance) will calculate the balance for... this customer number in the current client 1! No matter which customer this is! Because also the necessary Detailed Cust. Ledg. Entry (Table 339) are taken from the client of the user context, i.e. from the active client! If there is this customer number (better: the Detailed Cust. Ledg. Entry (Table 339) entries) for this customer does not exist in the current client, the result is simply 0, even if this customer has a balance in the client Client2.

The solution for the CalcFields problem is quite simple: The Calcfield must "simply" be emulated over the corresponding table, e.g.

CustLedgEntry.setrange(„Customer No.“, „customer number“);
CustLedgEntry.Calcsums(Amount);

But what do we do with the item posting sheet or any other posting sheet? Or generally when inserting records? Records can be initialized with templates, for example. Since the standard Navision logic does not perform a client change for triggers, ANY relevant logic would have to be reprogrammed with the necessary client change. A simple "Table.ChangeCompany(NewClient, Global=True)" would make our lives so much easier...

Well... unfortunately, there is no such thing. Well... There might be!

Complete client change with all triggers!

StartSession(SessionID,CodeunitNo,Company,Record) allows to start any code unit as a new session - in any client. Since the client (=Company) is valid for one session, the client given as parameter is valid for the whole runtime of this code unit - and all objects, triggers etc. called in or by this code unit.

So the key is to read the files to be imported into the respective temporary tables for each client in the import example above without any triggers at all, and then pass this temporary table to a start session.

Attention! If this is to happen in one go, then you also need temporary tables (e.g. as array), or you have to store the valid client in the table as well, and then react correctly in the called code unit!
So it would be


Case AktuelleZeileMandant of
'1' : TempTableClient1.FieldValue = Any;
'2' : TempTableClient2.FieldValue = Any;
end;

or

TempTableClient[CurrentRowClient].FieldValue = Any;

However, it is much more elegant to move the complete read-in logic / processing logic directly into the new session:

SessionStartetAt := CREATEDATETIME(WORKDATE,TIME);
STARTSESSION(SessionEvent."Session ID",50010,pCompanyCode,ImportAS400);
Log.Log(ObjRef,'SessionStart'+ pCompanyCode,SessionEvent."Session ID",'');
SLEEP(100);
ActiveSession.SETRANGE("Session ID",SessionEvent."Session ID");
WHILE ActiveSession.FINDFIRST DO BEGIN
SessionDuration := ROUND((CREATEDATETIME(WORKDATE,TIME) - SessionStartetAt) / 1000,1,'<');
SessionWindow.UPDATE(2,ROUND(SessionDuration / pEstDurationS * 10000,1,'<'));
SessionEvents.SETRANGE("Session Unique ID",ActiveSession."Session Unique ID");
SLEEP(1000);
END;
Log.Log(ObjRef,'Session Duration '+pCompanyCode,SessionDuration,pFileName);
SessionEvents.SETCURRENTKEY("Session Unique ID");
IF SessionEvents.FINDLAST THEN
Log.Log(ObjRef,'SessionEnd '+ pCompanyCode,SessionEvents.Comment,pFileName);

Of course, this is only the basic framework. If you have a need here, please contact me.
Especially in the area of AS/400 data transfers , there is often the need to read tables that are output across clients into separate clients in Navision Financials or Business Central. Please keep in mind that you should a) never set a standard Navision table to "Cross-Client = Yes". You are opening a Pandora's box. And b) That you can't do that since BC 15... fortunately.

But... how do you know when this session is ready?

This is the automatic following problem with the sessions. They are self-sufficient. They work on their own. When you are done, they are done.
The simplest thing is to build the tables to be processed in such a way that you can fire them off with start session, and at some point they are just finished, and that's it. Of course, this does not fit if the data to be imported build on each other. E.g. first the article master data, then the corresponding transaction data.

But if you really want to know when they are finished, you need something like a return value. StartSession even provides it: OK := StartSession(...
However OK returns only whether the session could be started, not whether it was terminated correctly. So this is no comparison to the known OK=Codeunit.run..., which a) waits, if the codeunit was finished, and b) also returns a result: TRUE: Correctly processed FALSE: An error occurred in the processing, which would have led to an error. There is no such thing with StartSession.

Well... Fortunately Navision / Business Central still has enough roots of Navision, so that you can still form a workaround around the increasingly complicated Microsoft part.

The code unit started with start session in Navision or Business Central enters a new value in the session table! And at termination an entry in the SessionEvents, if available with termination error message.

Here is a tip: Enter the two existing pages 670 & 9506 as well as a new page on the table 2000000111 Session Event directly into the menu site under Application Tools:

Screenshot vom Navision / Business Central RTC mit den Sitzungsinformationen (Sessioninfos) der Seiten 670, 9506 und der nicht im Standard vorhandenen Session Eventtabelle
Example for the extension of the tool menu of Navision / Business Central RTC with the session information (Sessioninfos) of the pages 670, 9506 and the session event table, which cannot be displayed in the standard system.

In the above example, waiting for the freshly started client-specific session to end is already built in!