Header Image

Translate

vrijdag 3 juli 2015

Actie uitvoeren in een actieve Ax client van extern

Soms is het wenselijk dat Ax iets doet, wat wordt getriggerd vanuit een externe applicatie. Een proces dat communiceert met een andere proces wordt ook wel IPC (Inter-process-communication) genoemd. Een voorbeeld; de telefoon gaat. Op basis van het telefoonnummer kan worden herleid welke klant belt en je wilt direct zien wat er speelt bij een klant. Nu heeft Ax wel een TAPI interface, maar als dat niet mogelijk is…
Standaard heeft Ax een zogenaamde ‘named pipe’. Een named pipe kan je zien als een message-queue in standaard Windows die je aanmaakt en een listener op zet. Beetje vergelijkbaar met een map die actief gepolled wordt; zodra er een bestand binnenkomt wordt dit herkent en kan je er iets mee. Er staat al een goede blog van Max Belugin online die dit omschrijft: http://axcoder.blogspot.nl/2010/10/how-to-open-form-in-running-ax-from.html

Samengevat:
Elke Ax client maakt named-pipe aan met het formaat: Dynamics\Event\0S-1-5-5-x-xxxxxx
  • Dynamics\Event – dit is een vaste waarde
  • 0 – dit is de waarde ingericht in Ax. Basis \ Instellingen \ Waarschuwingen \ Waarschuwingsparameters:
image
  • S-1-5-5-x-xxxxxx – dit is een Security Id (SID) voor de logon sessie. Dit is dus _niet_ de SID van de gebruiker. Dit is even interessant: de sessie is per gebruiker uniek op een terminal server, zodat de named pipe expliciet voor die gebruiker is en niet andere gebruikers gaat aansturen. Maar: als een gebruiker meerdere Ax sessies open hebt staan (zelfde of meerdere omgevingen), dan lijkt het de langst-actieve client die de queue afhandelt. Met de vorige parameter (Doel voor drilldown) zou je wel een test-omgeving een andere Id kunnen geven. 
De blog van Max omschrijft het mooi met een voorbeeld .NET applicatie. Daar ga ik dan ook niet verder op in. Het ophalen van de logon-session-SID heb ik even bekeken, maar ik kan het zo ook niet mooier maken.

Wat iets meer in de oplossing zit van Max maar niet is verwoord, is wat er nou gebeurt in Ax:
1. De class ‘EventDrillDownPoller’, methode ‘scheduledPoll’
1 void scheduledPoll() 2 { 3 str pipeData; 4 SysStartUpCmd cmd; 5 ; 6 if (pipe) 7 { 8 pipeData = pipe.read(); 9 if ('' != pipeData) 10 { 11 cmd = SysStartUpCmd::construct(pipeData); 12 if (cmd) // INT - check null value 13 cmd.infoRun();  14 this.createPipe(); // Re-create the pipe in order to be ready for the next command. 15 } 16 infolog.addTimeOut(this, methodstr(EventDrillDownPoller, scheduledPoll), #idleTimeBetweenExternalDrillDownPolls); 17 }  18 }
Deze methode wordt regelmatig (<1 seconde) indien de Ax client is idle uitgevoerd. Regel 12 heb ik toegevoegd; de SysStartupCmd kan namelijk null terug geven als het commando niet herkend wordt. Op zich is de nullpointer fout iets dat je een gebruiker niet wilt geven, maar het echte probleem is dan dat de createPipe en addTimeOut niet aangeroepen wordt en daarmee stopt deze queue. Het is ook best-practice om data (en commando’s) van externe applicaties te valideren voordat je deze verwerkt, zou ik zeggen.

2. De SysStartUpCmd en afstammeling doet eigenlijk het werk:

1 static SysStartupCmd construct(str startupCommand)   2 {   3 str s = strLRTrim(startupCommand);   4 int p = strscan(s,'_',1,strlen(s)); 5 str parm; 6 SysStartupCmd sysStartupCmd;   7 ;   8 if (p) 9 { 10 parm = substr(s,p+1,strlen(s)); 11 parm = strrem(parm,'"'); 12 s = substr(s,1,p-1); 13 }  14 setprefix(s); 15 switch (s)  16 {  17 case 'setbuildno':  18 sysStartupCmd = new SysStartupCmdBuildNoSet(s,parm); 19 break;  20 ...  21 case 'callincomming': 22 // INT - process call 23 sysStartupCmd = new SysStartupCmdCallIncomming(s,parm);  24 break;  25 } 26 if (sysStartupCmd)  27 {  28 if (sysStartupCmd.canRun()) 29 return sysStartupCmd;  30 error(strfmt("%1, %2", s, parm));  31 error("@SYS81158"); 32 }  33 return null; 34 }
Regels bij 20 even voor de overzichtelijkheid wat code weggehaald. In deze methode wordt op basis van de aangeleverde waarde een commando uitgevoerd. Hier zit je al in de actieve thread van Ax en kan je alles doen. Open een form, geef een alert, maak een record aan, wat je wil. Maar je kan het ook doen zoals de reeds bekende commando’s, maak een SysStartupCmd subclass aan en beperkt de aanpassing tot een extra case in de switch. Nu het commando: deze bestaat uit minimaal één en maximaal twee delen: <command>_<parameter>, dus met underscore. In dit voorbeeld: als je het commando “callincomming_0221234567” dan splitst standaard Ax in regels 10 t/m 12 het commando en wordt de parameter als zodanig doorgegeven. Je kan zelf dit weer met een sub-scheidingsteken splitsen en in je eigen class verwerken.

Of als je veel commando’s denkt nodig te hebben, dan kan je de code beperken met de volgende switch:
1 ClassName className;  2 ClassId classId;   3 SysDictClass sysDictClass;   4 ;  5 ...   6 default:   7 // INT - use action name for classname 8 className = classid2name(classnum(SysStartupCmd));   9 className += s;  10 classId = classname2id(className);  11 sysDictClass = new SysDictClass(classId); 12 if (sysDictClass 13 && sysDictClass.allowMakeObject()) 14 {  15 sysStartupCmd = sysDictClass.makeObject(s,parm);  16 }  17 break;

donderdag 2 juli 2015

Workflow Werknemer-ID is niet ingesteld of de record voor %1, %2 is niet gevonden.

Bij het aanbieden van bijvoorbeeld een inkooporder aan de workflow worden verschillende automatische controles uitgevoerd. De foutmelding Werknemer-ID is niet ingesteld of de record voor %1, %2 is niet gevonden. geeft heel feitelijk aan wat er fout is.
Voor elke medewerker moet een bijbehorende gebruiker zijn aangemaakt. Of anders gezegd aan elke gebruiker moet een medewerker zijn gekoppeld.
Controleer bij systeembeheer, gebruiker



Optie Relaties:
Gebruiker zonder relatie
Voor deze gebruiker bestaat geen relatie, zoals de foutmelding "Werknemer-ID is niet ingesteld of de record voor %1, %2 is niet gevonden." ook duidelijk aangeeft.

De volgende situatie geeft echter ook dezelfde foutmelding:  

Gebruiker met een relatie
In de situatie hierboven wordt dezelfde foutmelding gegeven. Dit lijkt incorrect immers er is toch relatie voor de gebruiker? Inderdaad is aan de gebruiker een relatie gekoppeld. Echter de relatie is niet van het type werknemer. Dit is alleen te zien door het ontbreken van een afbeelding onder het kopje medewerker.

Gebruiker met een correcte relatie
Alleen indien een medewerker gekoppeld is aan de gebruiker zal de foutmelding Werknemer-ID is niet ingesteld of de record voor %1, %2 is niet gevonden. niet voorkomen. Het verschil met de voorgaande koppeling is het icoontje onder de kolom medewerker.