venerdì 15 aprile 2022

D365FFO - Gestione traduzioni

Gestire le traduzione di un testo in AX è un'operazione molto comune. Abbiamo a disposizione due framework. Il primo è quello che si appoggia sulla tabella LanguageTxt, già presente fin dalle primissime versioni; il secondo è quello che è stato introdotto sulla 2012 e si basa sulla classe SysTranslationHelper. In questo post vediamo come utilizzarli entrambe.

Allo scopo definiamo una tabella  codice,descrizione che chiameremo "LILtestTable" con la relativa form omonima.

1)LanguageTxt framework

Per prima cosa dobbiamo creare l'estensione della tabella LanguageTxt ed aggiungere la relation con la nostra tabella (possiamo copiare tutte le proprietà dalle relation già esistenti, si veda l'estensione  standard LanguageTxt.Extension) così:



A questo punto nella nostra form basta trascinare nel design il menù item display "LanguageTxt" ed impostare "LILTestTable"  nella proprietà "datasource".



Con questo metodo possiamo recuperare la traduzione:

 public static FreeTxt txt(  
     Voucher  _code,  
     LanguageId _languageId = CompanyInfo::languageId())  
 {  
      LILTestTable testTable = LILTestTable::find(_code);  
   
      LanguageTxt languageTxt = LanguageTxt::find(  
           testTable.TableId,  
           testTable.RecId,  
           _languageId);  
   
      return languageTxt.Txt ? languageTxt.Txt : testTable.Description;  
 }  

2)SysTranslationHelper framework

Dobbiamo creare una nuova tabella che chiameremo LILTestTableTranslation fatta così:


Questa nuova tabella và aggiunta come datasource alla form e messa in join deleayed con il datasource principale LILtestTable. Dobbiamo poi scrivere una classe fatta così:

 class LILTestTableTranslationHelper extends LedgerDimensionTranslationHelper  
 {  
   protected TableId getBackingEntityTranslationTableId()  
   {  
     return tableNum(LILTestTableTranslation);  
   }  
   
   protected Array getGetBackingEntityTranslatedFieldIds()  
   {  
     Array backingEntityTranslatedFieldIds;  
   
     backingEntityTranslatedFieldIds = new Array(Types::Integer);  
     backingEntityTranslatedFieldIds.value(1, fieldNum(LILTestTable, Description));  
   
     return backingEntityTranslatedFieldIds;  
   }  
   
   public static client void main(Args _args)  
   {  
     LILTestTableTranslationHelper TranslationHelper;  
   
     TranslationHelper = LILTestTableTranslationHelper::newFromArgs(_args);  
   
     TranslationHelper.launchTranslationDetailForm();  
   }  
   
   public static client LILTestTableTranslationHelper newFromArgs(Args _args)  
   {  
     LILTestTableTranslationHelper       TranslationHelper;  
     LILTestTable               LILTestTable;  
   
     if ( !(_args && _args.record() is LILTestTable))  
     {  
       throw error(strFmt("@SYS134996", tableStr(LILTestTable)));  
     }  
   
     LILTestTable = _args.record() as LILTestTable;  
   
     TranslationHelper = new LILTestTableTranslationHelper();  
     TranslationHelper.parmBackingEntity(LILTestTable);  
   
     return TranslationHelper;  
   }  
   
 }  

Ho preso la classe STD LedgerDimensionTranslationHelper perchè è una buona base generica che si presta molto all'estensione

Dobbiamo poi creare un menù item action che punta a questa classe ed inserirlo nel design della form:


Con questo metodo possiamo recuperare la traduzione:
 public Description localizedDescription(LanguageId _languageId = new xInfo().language())  
 {  
      Description       description;  
      LILTestTableTranslation translation;  
   
      if(this.RecId)  
      {  
           select firstonly translation  
           where translation.LILTestTable == this.RecId  
                && translation.Language == _languageId;  
             
           description = translation.Description;  
      }  
   
      if(!description)  
      {  
           description = this.Description;  
      }  
   
      return description;  
 }  

Conlusioni:
Come abbiamo visto il primo framework è molto più semplice del primo e di fatto non richiede codice.Lo svantaggio è che richiede di aggiungere ogni volta una nuova relation alla tabella languageTxt. 

Un vantaggio che ho riscontrato utilizzando il secondo framework (oltre ad essere più "user friendly")è che avendo una tabella dedicata risulta più semplice creare la relativa entity delle traduzioni

martedì 29 marzo 2022

AX 2012 - D365FFO - Refresh caller datasource (retain position)

Refreshare il DS chiamante è un'operazione  comune in AX. La versione 2012 ha introdotto il parametro "retain position" nel metodo research. A volte però neanche questo è sufficiente. Questo interessante post fà un pò di chiarezza sull'argomento:

http://devexpp.blogspot.com/2012/02/refresh-datasource-and-retain-position.html

giovedì 17 marzo 2022

D365FFO -- RSAT for WHS terminal

In order to implement RSAT using the WHS terminal, we have first of all to check if the following parameter is set (Enable RSAT support) at the following path Warehouse management > Setup >  Warehouse management parameters.


Once set the previous parameter, we should access the following path Warehouse management > Periodic task >  Warehouse app task validation.


In order to create a new test case we should click on the button new as reported above. Then once we have set the fields userid and description of the task we can start the recording of the tasks with warehouse app clincking on Start recording.


Once we started the registration we can do the activities to be recorded in the warehouse app.






Once we have done this procedure we must load the parameters from the recording clicking on the button Generate variables.


The system automatically fills the variables defined in the registration. In case we want to run the script we must access the same form and clicking Run task, as reported below.







martedì 8 marzo 2022

D365FFO - Check the number of rows in a table / add a total in a table / Group by a specific value

 In D365FO some new functions have been added in order to speed up the check of the number of lines / totals in the form.

For example accessing a specific form (purchase invoice form), we can add a footer in the form clicking with the left button of the mouse and selecting the function "Show footer".



 

The system will add in the bottom part of the form the total number of rows selected.

In case we want to group by a specific value of the form we can do it right clicking on the column and selecting "Group by this column" as reported below.


The system will show all the records grouped by the values shown in that field as reported below.


In case we want to calculate the total of a numerical field we can right click on that field and select the function Total this column.


The result will be the following one.




venerdì 17 dicembre 2021

AX 2012 - Disabilitare indice tabella via codice

Tutte le proprietà di un oggetto  dell'AOT che possono essere cambiate a mano, possono anche essere pilotate via codice usando la classe "TreeNode". Con questo job possiamo disabilitare l'indice della tabella "Accountant_BR"

 static void LILIndexDisable(Args _args)  
 {  
      TreeNode objTreeNode;  
      #Properties  
      objTreeNode = TreeNode::findNode(@"\Data dictionary\Tables\Accountant_BR\indexes\AccountantIdx");  
   
      if (objTreeNode)  
      {  
           objTreeNode.AOTsetProperty(#PropertyEnabled,"No");  
           objTreeNode.AOTsave();  
      }  
 }  

martedì 2 novembre 2021

D365FFO - Creazione indirizzo magazzino a partire dall'indirizzo di un site

 Con questo job è possibile creare un indirizzo (a partire da un indirizzo associato ad un site) e collegarlo ad un magazzino. Questo job è stato di grande aiuto:

http://axcorner.blogspot.com/2014/01/create-warehouse-addresses-through-code.html


   public static void main(Args _args)  
   {  
     InventSite                 inventSite;  
     InventLocation               inventLocation;  
     InventSiteId                inventSiteid = "K1";    //sito di partenza  
     InventLocationId              inventLocationId = "lil01"; //magazzino su cui creare l'indirizzo  
     LogisticsPostalAddress           postalAddress;  
     LogisticsPostalAddressView         postalAddressView;  
     LogisticsPostalAddress           logisticsPostalAddressSite;  
     LogisticsEntityLocationMap         locationMap;  
     container                  role, roleMap;  
     Map                     LocationRoleMap;  
     LogisticsLocation              logisticsLocation;  
     LogisticsElectronicAddress         LogisticsElectronicAddress;  
                         
   
     inventSite = inventSite::find(inventSiteid);  
     inventLocation = inventLocation::find(inventLocationId);  
   
     //quì prendo l'indirizzo sorgente del site  
     logisticsPostalAddressSite = inventSite.logisticsPostalAddress();  
             
     logisticsLocation = LogisticsLocation::find(logisticsPostalAddressSite.Location);  
     LogisticsElectronicAddress::findByLocation(logisticsLocation.RecId);  
       
   
     LogisticsPostalAddressEntity  postalAddressEntity = new LogisticsPostalAddressEntity();    
       
     postalAddressView.Street      = logisticsPostalAddressSite.Street;  
     postalAddressView.City       = logisticsPostalAddressSite.City;  
     postalAddressView.State       = logisticsPostalAddressSite.State;  
     postalAddressView.ZipCode      = logisticsPostalAddressSite.ZipCode;  
     postalAddressView.CountryRegionId  = logisticsPostalAddressSite.CountryRegionId;      
     postalAddressView.LocationName   = logisticsLocation.Description;  
   
     postalAddress    = postalAddressEntity.createPostalAddress(postalAddressView);  
         
     locationMap.initValue();  
   
     locationMap      = LogisticsEntityLocationMap::find(tableNum(InventLocationLogisticsLocation), inventLocation.RecId, postalAddress.Location);  
       
     locationMap.Entity       = inventLocation.RecId;  
     locationMap.Location      = postalAddress.Location;  
     locationMap.IsPostalAddress   = logisticsLocation.IsPostalAddress;  
     locationMap.IsPrimary      = LogisticsElectronicAddress.IsPrimary;  
     locationMap.IsPrivate      = LogisticsElectronicAddress.IsPrivate;  
   
     LocationRoleMap    = LogisticsLocationEntity::getDefaultLocationRoleFromEntity(tableNum(InventLocation));  
   
     roleMap        = map2Con(LocationRoleMap);  
     role         = conpeek(roleMap, 1);  
   
     locationMap.addEntityLocation(role ,true);  
   
     info("Terminato");  
   
   }  

giovedì 7 ottobre 2021

D365FO - New cut and paste options

 Is it possible to copy data directly with a cut and paste from excel for multiple lines?

Now yes, I'll show how.

Starting from an excel having all the lines properly ordered.


We copy using CTRL-C function the lines to be copied.

Then we open the correspondent form in D3650FO, create a new line.


And finally click CTRL-V.

It applies also to multiple rows, and in case of errors it is requested a correction by the user in interactive mode.