Et ønske, der er lige så gammelt som Navision: en generel klientændring via programkode. Og desværre er det forblevet et ønske den dag i dag ... eller er det?
Changecompany - Problem med triggere
En typisk opgave i Navision er f.eks. at importere data fra en enkelt fil til forskellige klienter. For eksempel importeres lagre fra et lagerstyringssystem (WMS), som tildeles forskellige klienter i ERP-systemet.
Også hvis du vil erstatte en mainframe-applikation fra 70'erne/80'erne, f.eks. Trampoline eller Quattro Pro på Siemens Nixdorf, så kender du/din applikation måske slet ikke begrebet „klient“.
Dybest set er denne læsning meget enkel. Udskiftning af mainframe er ikke så... Modtagertabellen er enten indstillet som DataPerCompany = NO, så alle dataposter kan læses direkte ind på én gang,
eller modtagertabellen efterlades med standardindstillingen DataPerCompany = , og klienten skiftes for hver datapost, der skal importeres, ved hjælp af ImportTable.changecompany(TargetClient for den datapost, der skal importeres).
Ingen af delene er en teknisk udfordring.
Det er dog vanskeligt a) at overføre disse data korrekt til artikel- eller bogarklinjerne i den respektive målklient og b) at bogføre denne tabel korrekt.
Hvorfor det? Alle, der har arbejdet med ChangeCompany, er allerede stødt på det.
Lad os tage dette enkle eksempel:
KLAR(genstand); //Vigtigt: skal placeres FØR ChangeCompany! Clear nulstiller ChangeCompany, i modsætning til Init. genstand.SKIFT VIRKSOMHED(Klient2) Vare.INDSÆT(sandt); //Det sande er det onde!
Formentlig vil denne kode fungere på nøjagtig samme måde.
Men hvad sker der?
Artiklen er i klient2 skabt, men med et artikelnummer fra den aktuelle klient!
Men hvorfor? Lad os se på InsertTrigger for ItemTable 27 i den overordnede sammenhæng:
KLAR(item);
genstand.SKIFT VIRKSOMHED(Klient2)
Vare.INDSÆT(sandt); ->
IF "Nej." = '' SÅ BEGYND
GetInvtSetup;
InvtSetup.TESTFIELD("Punktnumre"); //InvtSetup kommer fra klient1!
NoSeriesMgt.InitSeries(InvtSetup. "Item Nos.",xRec. "No. Series",0D, "No.", "No. Series"); //Nummerseriekodeenheden kører med alle poster i den i klient 1!
"Kalkulationsmetode" := InvtSetup. "Standard kalkulationsmetode"; //InvtSetup kommer fra klient1!
SLUT;
Der er ingen måde at ændre denne adfærd på. Enhver, der arbejder med ChangeCompany for første gang, vil næsten uundgåeligt støde på denne adfærd. Den normale forventning (efter min mening) er, at tabellen „ved“, at den arbejder i klient2, og derfor arbejder al programlogik i den automatisk og implicit med dataposter fra klient2.
Mhm... Spoiler alert: Det gør den ikke. Den aktive, globale klient gælder for sessionen, ikke for objektet. Så for den aktuelle brugerkontekst.
Et andet sjovt eksempel er dette:
Customer.get(‚Kundenummer“);
Kunde.SKIFT VIRKSOMHED(Klient2);
Customer.calcfields(Balance);
HVIS kunden med „kundenummeret“ findes i klient 2, vil calcfields(Balance) beregne saldoen for ... dette kundenummer i den aktuelle klient 1! Uanset hvilken kunde det drejer sig om! Dette skyldes, at den detaljerede Cust. Ledg. Entry (tabel 339) også tages fra brugerkontekstens klient, dvs. fra den aktive klient! Hvis dette kundenummer (bedre: den detaljerede Cust. Ledg. Entry (tabel 339)) for denne kunde ikke findes i den aktuelle klient, er resultatet simpelthen 0, selv om denne kunde har en saldo i klienten Client2 client.
Løsningen på CalcFields-problemet er ganske enkel: Calcfield skal „blot“ emuleres via den tilsvarende tabel, f.eks.
CustLedgEntry.sætområde(„Kundenummer“);
CustLedgEntry.Calcsums(Beløb);
Men hvordan gør vi med artikelbookingarket eller et hvilket som helst andet bookingark? Eller generelt, når vi indsætter poster? Poster kan f.eks. initialiseres med skabeloner. Da Navisions standardlogik ikke udfører en klientændring for triggere, skal HVER relevant logik omprogrammeres med den nødvendige klientændring. En simpel “Table.ChangeCompany(NewMandant, Global=True)" ville gøre vores liv så meget lettere ...
Tja ... det findes desværre ikke. Jo... der er en lille smule!
Komplet klientskifte med alle triggere!
StartSession(SessionID,CodeunitNo,Company,Record) gør det muligt at starte en hvilken som helst kodeenhed som en ny session - i en hvilken som helst klient. Da klienten (=Company) gælder for en session, gælder den klient, der er angivet som parameter, for hele køretiden for denne kodeenhed - og alle objekter, triggere osv. der kaldes i eller af denne kodeenhed.
Tricket er derfor at læse de filer, der skal importeres, ind i de respektive midlertidige tabeller pr. klient uden nogen triggere i ovenstående eksempel for importen, og derefter overføre denne midlertidige tabel til en startsession.
Vær opmærksom på dette! Hvis dette skal ske på én gang, skal du også bruge midlertidige tabeller (f.eks. som et array), eller du skal gemme den gyldige klient i tabellen og derefter reagere korrekt i den kaldte kodeenhed!
Så...
Case CurrentLineMandant af
'1' : TempTableMandant1.field value = Something;
'2' : TempTableMandant2.field value = Something;
slut;
eller
TempTableMandant[CurrentLineMandant].FieldValue = Anything;
Det er dog meget mere elegant at flytte hele læselogikken/behandlingslogikken direkte ind i den nye 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);
SLUT;
Log.Log(ObjRef,'Sessionsvarighed '+pCompanyCode,SessionDuration,pFileName);
SessionEvents.SETCURRENTKEY("Unikt sessions-id");
IF SessionEvents.FINDLAST THEN
Log.Log(ObjRef,'SessionEnd '+ pCompanyCode,SessionEvents.Comment,pFileName);
Dette er selvfølgelig kun de grundlæggende rammer. Hvis du har nogle krav, så kontakt mig.
Især inden for området AS/400 dataoverførsler Der er ofte behov for at importere tabeller, der udskrives på tværs af klienter, til separate klienter i Navision Financials eller Business Central. Husk, at a) du aldrig bør sætte en standard Navision-tabel til „Cross-client = Yes“. Det åbner Pandoras æske. Og b) at du ikke længere kan gøre dette siden BC 15 ... heldigvis.
Men ... hvordan ved du, hvornår denne session er færdig?
Det er det automatiske problem med sessionerne. De er selvforsynende. De arbejder selvstændigt. Når du er færdig, er du færdig.
Det enkleste er at konfigurere de tabeller, der skal behandles, på en sådan måde, at de kan startes med en start-session, og på et tidspunkt er de færdige, og det er fint. Det fungerer selvfølgelig ikke, hvis de data, der skal importeres, bygger på hinanden. For eksempel først artiklens stamdata og derefter de tilknyttede transaktionsdata.
Men hvis man virkelig vil vide, hvornår de er færdige, har man brug for noget, der ligner en returværdi. StartSession leverer endda dette: OK := StartSession(...
OK returnerer dog kun, om sessionen kunne startes, ikke om den blev afsluttet korrekt. Det kan derfor ikke sammenlignes med den velkendte OK=Codeunit.run..., som a) venter på at se, om kodeenheden er afsluttet, og b) også returnerer et resultat: TRUE: Korrekt behandlet FALSE: Der opstod en fejl under behandlingen, som ville have ført til en fejl. Der findes ikke noget sådant med StartSession.
Tja... Heldigvis har Navision/Business Central stadig nok Navision-rødder til at kunne skabe en workaround for den stadig mere komplicerede Microsoft-del.
Den kodeenhed, der startes med startsessionen i Navision eller Business Central, indtaster en ny værdi i sessionstabellen! Og i slutningen en post i SessionEvents, hvis tilgængelig med annulleringsfejlmeddelelse.
Her er et tip: Indtast de to eksisterende sider 670 & 9506 og en ny side i tabellen 2000000111 Session Event direkte på menusiden under Application tools:

I eksemplet ovenfor er det allerede indbygget, at man venter på, at den nystartede klientspecifikke session slutter!
