mercoledì 12 febbraio 2020

Notepad ++ Plug in x++

In questo post segnalo un utile plug in per l'editor Notepad++ per aggiungere tra i linguaggi l'x++.
Il plug è scaricabile da quì:

https://jrabascal.wordpress.com/2014/07/07/creating-user-defined-language-x-into-the-notepad/

Nel link trovate le instruzioni per l'installazione

Ho apportato qualche piccola modifica alle colorazioni delle parole chiavi, l'evidenziazione delle etichette e dei blocchi.

Potete scaricare il file da quì:

https://mega.nz/#!WE9lRY7S!6A-3Wf_vDcEFdFehS5Tikh3pf5cHUzDQfgaPbml0B4w

Questo un esempio:


giovedì 16 gennaio 2020

AX 2012 - SSRS - Errore su report parameter ‘AX_CompanyName’


Mi è capitato varie volte di imbattermi nel seguente errore quando vado a stampare il primo report dopo il riavvio dei servizi di reporting. L'errore scompare dalla seconda stampa. 

"The DefaultValue expression for the report parameter ‘AX_CompanyName’ contains an error: Request for the permission of type 'System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed. (rsRuntimeErrorInExpression)"

L'errore non risulta quindi bloccante ma è piuttosto fastidioso, soprattuto quando capita ad un utente. Quello che posso suggerirvi è di modificare come segue il file rssrvpolicy.config all'interno delle cartelle di SSRS, impostando l'autorizzazione "FullTrust" al posto di "Esegui" per "Report_Expressions_Default_Permissions".

Configurazione iniziale:
<CodeGroup
class="UnionCodeGroup"
version="1"
PermissionSetName="Execution"
Name="Report_Expressions_Default_Permissions"
Description="This code group grants default permissions for code in report expressions and Code element. ">
[...]
</CodeGroup>

Configurazione modificata:

<CodeGroup 
class="UnionCodeGroup" 
version="1" 
PermissionSetName="
FullTrust
Name="Report_Expressions_Default_Permissions" 
Description="This code group grants default permissions for code in report expressions and Code element. "> 

[..] 

</CodeGroup>



Fonte:

https://community.dynamics.com/ax/b/axsupport/archive/2012/02/02/microsoft-dynamics-ax-2012-reporting-extensions-error-system-security-permissions-environmentpermission-while-running-report

AX 2012 - Job Fieldname print

In this post I'll share a job made in order to print an infolog having all the fields of a table in ax with some useful information like, field name, label, help text, data type.

 static void AGIA_TableFieldNames_2(Args _args)    
 {    
      DictTable dt           = new SysDictTable(77);    
      FieldId _fieldId     = dt.fieldNext(0);    
      DictField                _dictField;    
      Dialog                dialog;    
      DialogField           myDialogField;    
      tablename                id;    
      dialog                     = new dialog();    
      myDialogField           = dialog.addField(extendedTypeStr(tablename), "TableName : ");  
      dialog.run();   
      if(dialog.closedOk())    
      {    
           id = myDialogField.value();    
           dt = new sysdicttable(tableName2id(id));    
           info(strFmt("Field Name; Field Label; Field Type; Field Help; Field Mandatory; Field Lenght"));    
           if(_fieldId)    
           while(_fieldId)    
           {    
                _dictField =dt.fieldObject(_fieldId);   
                if(_dictField)  
                {  
                     if(!_dictField.isSystem())  
                     {  
                          info(strFmt("%1; %2; %3; %4; %5; %6", _dictField.name(),_dictField.label(),_dictField.baseType(),_dictField.help(),_dictField.mandatory(), _dictField.stringLen()));    
                          _fieldId= dt.fieldNext(_fieldId);  
                     }  
                }  
           }    
           else  
           {  
                info(strFmt("Missing table %1", id));  
           }                 
      }    
 }   
Con questo job invece possiamo stampare il contenuto di una tabella (lista dei campi e valore) in maniera dinamica:
 static void LIL_PrintTableValue(Args _args)  
 {  
   DictTable  dt;  
   DictField  dictField;  
   Common   common;  
   Counter   i=0;  
   FieldId   fieldId;  
       
   dt = new DictTable(tableNum(InventSite));  
   
   common = dt.makeRecord();  
    
   while select common  
   {  
       
     fieldId = dt.fieldNext(0);  
       
     while (fieldId)  
     {  
       dictField = dt.fieldObject(fieldId);  
         
       if(dictField.configurationKeyId() != configurationkeynum(SysDeletedObjects60))  
       {  
         info(strFmt("%1 = %2",fieldId2name(dt.id(),dictField.id()),common.(dictField.id())));  
       }  
         
       fieldId = dt.fieldNext(fieldId);  
     }  
       
     info("---------------------");  
   }  
 }  

mercoledì 26 giugno 2019

AX 2012 - Job per creazione progetto da CSV

In questo post pubblico un job che ho scritto che serve per creare un progetto privato leggendo un csv contenente una lista di tipi e nomi degli elementi dell'AOT

 static void LIL_ProjectElementAdd(Args _args)  
 {  
   TextIo         inFile;  
   container        line;  
   Counter         records;  
   SysOperationProgress  simpleProgress;  
   container        fileContainer;  
   Counter         loopCounter;  
   CustTable        CustTable;  
   InventTable       InventTable;  
   str           filename,  
               properties,  
               objectType,  
               objectName,  
               aotType,aotPath;  
   TreeNode        projectNode,  
               tempNode;  
   ProjectGroupNode    groupNode;  
   boolean         managed;  
   
   #OCCRetryCount  
   #AviFiles  
   #File  
   #aot  
   #DMF  
   
   filename = WinAPI::getOpenFileName(0,  
                 [WinAPI::fileType(#csv),#AllFilesName + #csv],  
                 strFmt(@'C:\users\%1\Desktop',WinApi::getUserName()),  
                 "@SYS53008"  
                 );  
   try  
   {  
     inFile = new TextIo(filename, 'r');  
     inFile.inRecordDelimiter('\n');  
     inFile.inFieldDelimiter(';');  
     while (inFile.status() == IO_Status::OK)  
     {  
       fileContainer += [infile.read()];  
     }  
     inFile = null;  
   }  
   catch  
   {  
     throw error(strFmt("@SYS18678", filename));  
   }  
   
   simpleProgress = SysOperationProgress::newGeneral(#aviUpdate, "Importazione...", conLen(fileContainer));  
   
   records = 0;  
   
   projectNode = SysTreeNode::createProject("MyPrivateProject");  
   
   groupNode  = projectNode.AOTadd("Object");  
   properties = groupNode.AOTgetProperties();  
   
   for (loopCounter = 2; loopCounter <= conLen(fileContainer) - 1 ; loopCounter++)  
   {  
     line = conPeek(fileContainer,loopCounter);  
     objectType = conPeek(line,1);  
     objectName = conPeek(line,2);  
   
     switch(objectType)  
     {  
       case "BaseEnum" :  
         aotType = #BaseEnums;  
         aotPath = #BaseEnumsPath;  
         managed = true;  
         break;  
   
       case "Class" :  
         aotType = #Classes;  
         aotPath = #ClassesPath;  
         managed = true;  
         break;  
   
       case "ExtendedDataType" :  
         aotType = #ExtendedDataTypes;  
         aotPath = #ExtendedDataTypesPath;  
         managed = true;  
         break;  
   
       case "Form" :  
         aotType = "Forms";  
         aotPath = #FormsPath;  
         managed = true;  
         break;  
   
   
       case "Macro" :  
         aotType = "Macros";  
         aotPath = #MacrosPath;  
         managed = true;  
         break;  
   
       case "Menu" :  
         aotType = "Menus";  
         aotPath = #MenusPath;  
         managed = true;  
         break;  
   
   
       case "Table" :  
         aotType = "Tables";  
         aotPath = #TablesPath;  
         managed = true;  
         break;  
   
       case "Map" :  
         aotType = "Maps";  
         aotPath = #TableMapsPath;  
         managed = true;  
         break;  
   
       case "Query" :  
         aotType = #Queries;  
         aotPath = #QueriesPath;  
         managed = true;  
         break;  
   
       case "SecurityDuty" :  
         aotType = "SecDuties";  
         aotPath = #SecDutiesPath;  
         managed = true;  
         break;  
   
       case "SecurityPrivilege" :  
         aotType = "SecPrivileges";  
         aotPath = #SecPrivilegesPath;  
         managed = true;  
         break;  
   
       case "SecurityPrivilege" :  
         aotType = "SecPrivileges";  
         aotPath = #SecPrivilegesPath;  
         managed = true;  
         break;  
   
       case "SecurityProcessCycle" :  
         aotType = "SecProcessCycles";  
         aotPath = #SecProcessCyclesPath;  
         managed = true;  
         break;  
   
       case "SecurityRole" :  
         aotType = "SecRoles";  
         aotPath = #SecRolesPath;  
         managed = true;  
         break;  
   
       case "SSRSReport" :  
         aotType = "SSRSReports";  
         aotPath = #SSRSReportsPath;  
         managed = true;  
         break;  
   
       case "VisualStudioProjectCSharp" :  
         aotType = "VSProjectsCShar";  
         aotPath = #VSProjectsCSharpPath;  
         managed = true;  
         break;  
   
       default:  
         managed = false;  
         warning(strFmt("Unable to add %1, %2",objectType,objectName));  
     }  
   
     if(objectName && managed)  
     {  
       tempNode = TreeNode::findNode(aotPath);  
       groupNode.addNode(tempNode.AOTfindChild(objectName));  
     }  
   }  
   projectNode.AOTsave();  
 }  

L'output sarà il seguente:


martedì 11 giugno 2019

AX 2012 - Job dimensioni finanziarie

In questo post vediamo due job per manipolare le dimensione finanziarie ed assegnare il risultato ad una riga di general journal

Il primo job, data una ledger dimension cambia l'account e valida la nuova dimensione:

 static void LIL_ChangeAccountNum(Args _args)  
 {  
   LedgerJournalTrans           ledgerJournalTrans;  
   DimensionDefault            dimensiondefault;  
   LedgerDimensionAccount         LedgerDimensionAccount, ret;  
   DimensionValidationStatus        status;  
   MainAccountNum             newMainAccountnum = "10505005";  
   
   ledgerJournalTrans = ledgerJournalTrans::findRecId(5644985326, false);  
   
   dimensiondefault = DimensionDefaultingEngine::getDefaultDimension(DimensionDefaultingEngine::getDimensionSourcesForLedgerDimension(ledgerJournalTrans.ledgerdimension));  
   
   LedgerDimensionAccount = AxdDimensionUtil::getLedgerAccountId([newMainAccountnum,newMainAccountnum]);  
     
   ret = DimensionDefaultingService::serviceCreateLedgerDimension(LedgerDimensionAccount,dimensiondefault);  
   
   status = DimensionValidation::validateByTree(ret,today(),true);  
     
   if(status == DimensionValidationStatus::Valid)  
   {  
     ledgerJournalTrans.LedgerDimension = ret;  
   }  
 }  

il secondo crea una ledger dimension contente solamente il bank account num:

 static void LIL_LedgerDimensionWhitAccountOnly(Args _args)  
 {  
   LedgerJournalTrans     ledgerJournalTrans;  
   DimensionDynamicAccount   DimensionDynamicAccount;  
     
   DimensionDynamicAccount = DimensionStorage::getDynamicAccount("BANK001", LedgerJournalACType::Bank);  
     
   ledgerJournalTrans.LedgerDimension = DimensionDynamicAccount;  
 }  


mercoledì 5 giugno 2019

D365 - Script for checking the code executed while inserting data in a table manually

In order to have a quick list of fields inserted in a form we can proceed in the following way.
First insert a record in the table we want to analyse.



At this point access the following button Option --> Record info.


Click on the button Script as reported below.

The following txt document is created, in which the procedures for inserting a row in the table previously filled manually are retrieved.



lunedì 20 maggio 2019

AX 2012 - Creare batch job tramite x++ e aggiunta dipendenze

In questo post vediamo come creare dei batch job schedulabili e suddividere l'esecuzione in task. Sfruttando inoltre le "Batch Dependency" faremo in modo che ogni task parta solo se il precedente termina ( oppure và in errore ). Allo scopo creiamo una semplice classe sysoperation (LIL_WhoAmIService più relativa controller / contract) che prende come parametro un intero e lo restiuisce tramite info log. Il job seguente effettua tutte le operazioni di schedulazione / aggiunta dipendenze:

 static void LIL_TestRunDependency(Args _args)  
 {  
   BatchHeader       batchHeader   = BatchHeader::getCurrentBatchHeader();  
   List          list      = new List(Types::Class);  
   ListEnumerator     le;  
   LIL_WhoAmIController  batchTask,prev;  
   Counter         i;  
   BatchInfo        batchInfo;  
   
   batchHeader = batchHeader::construct();  
   
   batchHeader.parmCaption("Task dependency execution");  
   
   //creo 10 instaze della mia classe, ciascuna con il suo id  
   for(i = 0; i < 10; i++)  
   {  
     batchTask = new LIL_WhoAmIController(classStr(LIL_WhoAmIService),  
                        methodStr(LIL_WhoAmIService, printWhoAmi));  
   
     batchTask.parmWhoAmi(i);  
   
     batchTask.init();  
   
     //in caso di sysoperation è neccessario impostare execution mode = Synchronous  
     //altrimenti le dipendence non vengono rispettate e i task partono tutti  
     //allo stesso momento  
     batchTask.parmExecutionMode(SysOperationExecutionMode::Synchronous);  
   
     batchInfo = batchTask.batchInfo();  
   
     batchInfo.parmCaption(strFmt("Task N%1",batchTask.parmWhoAmi(i)));  
   
     list.addStart(batchTask);  
   }  
   
   //Creazione dipendenze  
   le = list.getEnumerator();  
   while (le.moveNext())  
   {  
     batchTask = le.current();  
   
     if(!prev)  
     {  
       //il primo task non avarà voncoli  
       batchHeader.addRuntimeTask(batchTask,BatchHeader::getCurrentBatchTask().RecId);  
     }  
     else  
     {  
       //i successivi dipendono dal precedente  
       batchHeader.addRuntimeTask(batchTask,BatchHeader::getCurrentBatchTask().RecId);  
       batchHeader.addDependency(prev,batchTask,BatchDependencyStatus::FinishedOrError);  
     }  
   
     prev = le.current();  
   }  
   
   batchHeader.save();  
   
   info("Done");  
 }  

Se lanciamo il job ed aspettiamo la fine dell'esecuzione dei 10 task, possiamo vedere il risultato in batch history:



L'esecuzione ha generato 10 tasks. il Task "N0" come vediamo non ha condizioni. Tutti gli altri dipendono dal precedente, Ad esempio il task N5 dipende dal task N4 e così via.