giovedì 10 settembre 2020

AX2012 - How to extract TMSCarrier addresses and addresses roles

 If you need to extract from an AX2012 R3 the full list of addresses and their roles you can use the following job.


 static void A_GiaImportCarrierAddresses(Args _args)  
 {  
      TMSCarrier             TMSCarrier;  
      LogisticsPostalAddress       LogisticsPostalAddress;  
      Tmscarrierlogisticslocation     TMScarrierlogisticslocation;  
      TMSCarrierLogisticsLocationRole   TMSCarrierLogisticsLocationRole;  
      LogisticsLocation          LogisticsLocation;  
      LogisticsLocationRole        logisticslocationrole;  
   
      while select TMScarrier  
           join TMScarrierlogisticslocation  
           where TMSCarrier.recid == TMScarrierlogisticslocation.Carrier  
           join LogisticsPostalAddress  
           where LogisticsPostalAddress.Location == TMScarrierlogisticslocation.Location  
           join LogisticsLocation  
           where LogisticsLocation.recid == LogisticsPostalAddress.Location  
           join TMSCarrierLogisticsLocationRole  
           where TMSCarrierLogisticsLocationRole.LocationLogisticsLocation == TMScarrierlogisticslocation.recid  
           join LogisticsLocationRole  
           where logisticslocationrole.RecId == TMSCarrierLogisticsLocationRole.LocationRole  
   {  
     info( strFmt("%1; %2; %3; %4; %5; %6",LogisticsPostalAddress.street,  
                                                         LogisticsPostalAddress.ZipCode,  
                                                         LogisticsPostalAddress.City,  
                                                         LogisticsPostalAddress.CountryRegionId,   
                                                         LogisticsLocation.Description,  
                                                         logisticslocationrole.Name));  
   }  
 }  


The result will be the following one.


domenica 10 maggio 2020

AX 2012 - Esplosione albero BOM in x++

In questo post vediamo come esplodere la BOM di un articolo. Ho preso spunto da questi due post e li ho rielaborati:


Il primo è interessante perchè mostra come usare la classe BOMSearch, però non è utilizzabile in quanto in AX 2012 l'enumerato InventTable.ItemType == ItemType::BOM è deprecato, il secondo è a mio avviso incompleto perchè espande la BOM più del necessario. Il fatto che un articolo abbia una BOM valida è condizione necessaria ma NON sufficiente per poterlo esplodere,  occorre che siano vere queste condizioni:

1) InventTable.PmfProductType  = BOM



2) In default order settings e anche in copertura articoli, abbiamo il default order type che ci dice se è Production o Purchase. Il campo è InventItemSetupSupplyType.DefaultOrderType


Il metodo BOMDesignerCtrl::itemCanHaveBOM(), usato nel "BOM designer" standard effettua questi due controlli.

Ecco la classe che effettua l'esplosione:

 class LIL_BomExplosionBOMSearch  
 {  
   BOMVersion   bomVersion;  
 }  

 public static LIL_BomExplosionBOMSearch construct(BOMVersion _bomVersion)  
 {  
   LIL_BomExplosionBOMSearch instance = new LIL_BomExplosionBOMSearch();  
   instance.parmBomVersion(_bomVersion);  
   return instance;  
 }  

 public void run()  
 {  
   BOMSearch search = BOMSearch::newBOMTree(this.parmBomVersion().BOMId,  
                        today(),  
                        this.parmBomVersion().ItemId,  
                        this.parmBomVersion().inventDim().configId);  
   this.BomExplode(search,0);  
 }  

 private boolean hasValidBomVersion(ItemId _itemId)  
 {  
   BOMVersion bomVersionLocal;  
   ;  
   select firstonly RecId from bomVersionLocal  
       where bomVersionLocal.ItemId == _itemid  
       && bomVersionLocal.Active  
       && bomVersionLocal.Approved  
       && bomVersionLocal.FromDate <= systemdateget ()  
       && (!bomVersionLocal.ToDate || bomVersionLocal.ToDate >= systemdateget ());  
   if (bomVersionLocal.RecId)  
   {  
     return true;  
   }  
   return false;  
 }  

 void BomExplode(BOMSearch _search, BOMLevel _level = 0)  
 {  
   BOM     bom;  
   BOMSearch  BomSearchLocal;  
   InventTable inventTable;  
   BOMId    bomId;  
   ;  
   //max tree deep level reached, trow error  
   if(_level > BOMParameters::find().BOMMaxLevel)  
   {  
     throw error("@SYS26729");  
   }  
   //root Item  
   if (_level == 0)  
   {  
     info(strFmt("%1 - %2",_level,this.parmBomVersion().ItemId));  
   }  
   while (_search.next())  
   {  
     bom = _search.BOM();  
     inventTable = bom.inventTable();  
           info(strFmt("%1 - %2",_level,bom.ItemId));  
     //verify if item can be exploded  
     if (BOMDesignerCtrl::itemCanHaveBOM(bom.inventTable()) &&  
       this.hasValidBomVersion(bom.ItemId))   
     {  
       //find active bom  
       bomId = inventTable.bomId(today(),bom.BOMQty,bom.inventDim());  
       BomSearchLocal = BOMSearch::newBOMTree(bomId,  
                     today(),  
                     bom.ItemId,  
                     bom.inventDim().configId);  
       this.BomExplode(BomSearchLocal, _level+1);  
     }  
   }  
 }  

La chiamata alla classe:

 LIL_BomExplosionBOMSearch     LIL_BomExplosionBOMSearch = LIL_BomExplosionBOMSearch::construct(bomVersion);  
 LIL_BomExplosionBOMSearch.run();  

Come nota tecnico / teorico, secondo la teoria degli alberi, questa ricerca sull'albero BOM è una ricerca di tipo DFS (Depth-first search) perchè non appena trova un nodo a livello (n) passa subito al suo primo figlio a livello (n+1):


lunedì 16 marzo 2020

D365 FFO - lookup su formcontrol unbound

Questo link mostra come fare la lookup via CoC sul campo di un datasource:

https://community.dynamics.com/365/financeandoperations/f/dynamics-365-for-finance-and-operations-forum/357208/lookup-on-form-datasource-field

Per fare la lookup su un controllo unbound dobbiamo fare così:

 [ExtensionOf(formControlStr(ProjTableWizard,FormControl))]  
 final class LILProjTableWizard_Extension  
 {  
   public void lookup()  
   {  
     next lookup();
  
     Query query = new Query();  

     query.addDataSource(tableNum(CustGroup));  
     
     SysTableLookup sysTableLookup = SysTableLookup::newParameters(tableNum(CustGroup),this);  
     systableLookup.addLookupfield(fieldNum(CustGroup, CustGroup), true);  
     systableLookup.addLookupfield(fieldNum(CustGroup, Name), false);  
     
     sysTableLookup.parmQuery(query);  
     sysTableLookup.performFormLookup();  
   }  
 }  

In questo modo invece possiamo effettuare la CoC sul metodo clicked di un pulsante:

 [ExtensionOf(formControlStr(WHSShipPlanningListPage,Transfer))]  
 final class MCSWHSShipPlanningListPageTransfer_Extension  
 {  
   void clicked()  
   {  
     FormDataSource      WHSShipmentTableDS;  
     FormControl      control = this as FormControl;  
     WHSShipmentTableDS = control.formRun().dataSource(formDataSourceStr(WHSShipPlanningListPage,WHSShipmentTable)) as FormDataSource;  
     next clicked();  
     WHSShipmentTableDS.research(true);  
   }  
 }  

domenica 1 marzo 2020

D365FFO - Cercare l'id dell'etichetta in base al testo

In questo post vediamo un job (runnableClass) per cercare il codice dell'etichetta in base al testo. Nel esempio vogliamo cercare tutte le etichette che contengono "Data Fattura":

Questi due post sono stati di grande aiuto:

Questo mostra come ricavare il codice dell'etichetta cercando in base al testo:

https://community.dynamics.com/365/financeandoperations/f/dynamics-365-for-finance-and-operations-forum/272482/how-to-get-label-id-from-a-label-text/810927

Questo post invece indica come ricavare tutti i fileID delle etichette:

https://community.dynamics.com/365/financeandoperations/f/dynamics-365-for-finance-and-operations-forum/243050/enumerate-label-prefix-for-label-search-functionality


 Questo è il job preso dal primo link e modificato:

   public static void main(Args _args)  
   {  
     int j;  
     //parametri - start  
     str TextToFind = "Data fattura"; //stringa da cercare  
     LanguageId languageId = "it";    //lingua  
     boolean matchExactly = true;     //trova parola esatta oppure no  
     str   LabelFile = "SYS";         //Label file Id  
     //parametri - end  
     container result;  
     Map txtToLabelIdMap = new Map(Types::String, Types::String);  
     ClrObject labels = new ClrObject("System.Collections.Generic.Dictionary`2[System.String,System.String]");  
     System.Globalization.CultureInfo cultInfo = System.Globalization.CultureInfo::CreateSpecificCulture(languageId);  
     labels = Microsoft.Dynamics.Ax.Xpp.LabelHelper::GetAllLabels(LabelFile, cultInfo);  
     if(labels)  
     {  
       ClrObject labelsEnumerator = labels.GetEnumerator();  
       while(labelsEnumerator.MoveNext())  
       {  
         ClrObject keyValuePair = labelsEnumerator.get_Current();  
         var currentlabelId = keyValuePair.get_Key();  
         var currentTxt = keyValuePair.get_Value();  
         if(matchExactly)  
         {  
           if(currentTxt == TextToFind)  
           {  
             if (!Microsoft.Dynamics.Ax.Xpp.LabelHelper::IsLegacyLabelId(currentlabelId))  
             {  
               currentlabelId = strFmt("%1%2:%3","@",LabelFile,currentlabelId);  
             }  
             result += strFmt("%1,%2 -> %3",LabelFile,currentlabelId,currentTxt);  
           }  
         }  
         else  
         {  
           if(strScan(currentTxt, TextToFind, 0, strLen(currentTxt)))  
           {  
             if (!Microsoft.Dynamics.Ax.Xpp.LabelHelper::IsLegacyLabelId(currentlabelId))  
             {  
               currentlabelId = strFmt("%1%2:%3","@",LabelFile,currentlabelId);  
             }  
             result += strFmt("%1,%2 -> %3",LabelFile,currentlabelId,currentTxt);  
           }  
         }  
       }  
     }  
     //container dei risultati  
     for(j = 1;j<=conLen(result);j++)  
     {  
       info(conPeek(result,j));  
     }  
   }  

Lanciando il job otterremo il seguente risultato: