martedì 24 ottobre 2023

D365 FFO - Creare una form di selezione / deselezione

In questo post vediamo come creare una form selezione / deselezione, come quelle che c'è sui batch group per esempio:



La form inoltre avrà anche un campo per poter filtrare il nome degli elementi

Per creare queste form AX mette disposizione l'oggetto "SysListPanelRelationTableCallback"

Allo scopo occorrono 3 Tabelle:

1) Anagrafica dei gruppi (LILGroupTable)

2) Anagrafica degli elementi che assegneremo di volta in volta ai gruppi (LILElementTable)

3) Tabella che contiene il legame (LILGroupElementTable), che sarà interemente gestita tramite l'oggetto "SysListPanelRelationTableCallback"

Ogni elemento inoltre potrà appartenere contemporaneamente a più di un gruppo alla volta.

Le tre tabelle saranno così fatte:




                                                 


Definiamo a questo punto una nuova form con il seguente design:



E che conterrà il seguente codice:
 [Form]  
 class LILGroupElementInfo extends FormRun  
 {  
   SysListPanelRelationTableCallback sysListPanel;  
   Num                groupId;  
   Description            elementNameFilter;  
   
   public void close()  
   {  
     sysListPanel.finalize();  
     super();  
   }  
   
   void init()  
   {  
     #ResAppl  
   
     sysListPanel = SysListPanelRelationTableCallback::newForm(element,  
                element.controlId(formControlStr(LILGroupElementInfo, ListPanelGroup)),  
                "@SYS113180", "@SYS78228", #ImageUser,  
                tablenum(LILGroupElementTable),  
                fieldnum(LILGroupElementTable, ElementId),  
                fieldnum(LILGroupElementTable, GroupId),  
                tablenum(LILElementTable),  
                fieldnum(LILElementTable, ElementId),  
                [fieldnum(LILElementTable, ElementId), fieldnum(LILGroupTable, Description)],  
                fieldNum(LILElementTable, Description), '',  
                identifierstr(Validate), '', '');  
     sysListPanel.parmDataRangeAsFilter(true);  
   
     super();  
   
     sysListPanel.init();  
   }  
   
   public edit str elementNameFilter(boolean _set, str _elementName)  
   {  
     if (_set && _elementName != elementNameFilter)  
     {  
       elementNameFilter = _elementName;  
     }  
   
     return elementNameFilter;  
   }  
   
   void tabChanged(int fromTab, int toTab)  
   {  
     #define.TabGroups(1)  
     #define.TabGroupElement(2)  
   
     groupId = LILGroupTable.GroupID;  
     switch (toTab)  
     {  
       case #TabGroups:  
         break;  
       case #TabGroupElement:  
         sysListPanel.parmDataRangeValue(elementNameFilter ? elementNameFilter : '*');  
         sysListPanel.parmRelationRangeValue(LILGroupTable.GroupID);  
         sysListPanel.parmRelationRangeRecId(LILGroupTable.RecId);  
         sysListPanel.fill();  
         break;  
     }  
   }  
   
   boolean validate(userId _userId, AddRemove _addRemove, SysListPanelRelationTable _listViewHandler)  
   {  
     return true;  
   }  
   
   [DataSource]  
   class LILGroupTable  
   {  
     public boolean validateWrite()  
     {  
       boolean ret;  
   
       str leftTrimedGroupId;  
       str rightTrimedGroupId;  
    
       leftTrimedGroupId = strltrim(LILGroupTable.GroupID);  
       rightTrimedGroupId = strrtrim(leftTrimedGroupId);  
   
       LILGroupTable.GroupID = rightTrimedGroupId;  
   
       ret = super();  
   
       return ret;  
     }  
   
     int active()  
     {  
       int ret;  
   
       ret = super();  
       element.elementNameFilter(true, '');  
       element.tabChanged(0, tab.tab());  
       return ret;  
     }  
   
   }  
   
   [Control("Tab")]  
   class Tab  
   {  
     void tabChanged(int fromTab, int toTab)  
     {  
       element.tabChanged(fromTab, toTab);  
       super(fromTab, toTab);  
     }  
   
   }  
   
   [Control("TabPage")]  
   class Groups  
   {  
     public boolean allowPageDeactivate()  
     {  
       boolean ret;  
   
       ret = super();  
   
       if (!LILGroupTable.RecId)  
       {  
         if (LILGroupTable_ds.validateWrite())  
           LILGroupTable.write();  
         else  
           return false;  
       }  
   
       return ret;  
     }  
   
   }  
   
   [Control("Button")]  
   class FilterButton  
   {  
     public void clicked()  
     {  
       super();  
   
       sysListPanel.parmDataRangeValue(elementNameFilter ? elementNameFilter : '*');  
       sysListPanel.fill(true);  
     }  
   
   }  
   
 }  

Il risultato sarà il seguente:





Se vogliamo creare una form di selezione / deselezione più semplice con un solo tab, l'oggetto da usare è il SysListPanel. Supponiamo di voler aggiungere ai parametri fornitori una tab dove poter selezionare / deselezionare le delivery mode:
 

Occorre:

1) Creare una tabella che conterrà i valori selezionati che chiameremo LILDlvModeAssociation così fatta:


2) Estendere la classe sysPanel così:

 public final class LILSysListPanel_VendDlvModeAssociation extends SysListPanel  
 {  
   public void addData(container _data)  
   {  
     int                 i;  
     LILDlvModeAssociation        relation;  
     DlvMode               dlvMode;  
     
     for (i = 1; i <= conlen(_data); i++)  
     {  
       relation.DlvModeRecId       = conpeek(_data, i);  
       relation.DlvModeId         = DlvMode::findRecId(relation.DlvModeRecId).Code;  
     
       relation.insert();  
      }  
   }  
   
   public container getData()  
   {  
     container                selectedData;  
     container                availableData;  
     LILDlvModeAssociation          relation;  
     DlvMode                 dlvMode;  
     
     while select TableId from relation  
       join RecId, Code from dlvMode  
       where dlvMode.RecId == relation.DlvModeRecId  
     {  
       selectedData += [[dlvMode.RecId, dlvMode.Code]];  
     }  
     
     while select RecId, Code from dlvMode  
       order by Code  
       notexists join relation  
         where relation.DlvModeRecId == dlvMode.recId   
     {  
       availableData += [[dlvMode.RecId,dlvMode.Code]];  
     }  
     
     return [availableData, selectedData];  
   }  
   
   public int numOfColumns()  
   {  
     return 2;  
   }  
   
   public void removeData(container _data)  
   {  
     int                 i;  
     RefRecId      dlvModeRecId;  
     LILDlvModeAssociation relation;  
     
     for (i = 1; i <= conlen(_data); i++)  
     {  
       ttsBegin;  
       dlvModeRecId = conpeek(_data, i);  
       delete_from relation  
         where relation.DlvModeRecId    == dlvModeRecId;  
       ttsCommit;  
     }  
   }  
   
   public static LILSysListPanel_VendDlvModeAssociation construct()  
   {  
     return new LILSysListPanel_VendDlvModeAssociation();  
   }  
   
   internal static SysListPanel newForm(FormRun _formRun,  
                     int   _parentId)  
   {  
     LILSysListPanel_VendDlvModeAssociation  sysListPanel = LILSysListPanel_VendDlvModeAssociation::construct();  
     
     sysListPanel.parmFormRun(_formRun);  
     sysListPanel.parmParentId(_parentId);  
     sysListPanel.parmCaptionSelected("@SYS113180");  
     sysListPanel.parmCaptionAvailable("@SYS179894");  
     sysListPanel.parmHasAddAllButton(true);  
     sysListPanel.parmHasRemoveAllButton(true);  
     sysListPanel.parmHasUpDownButton(true);  
     sysListPanel.build();  
     
     return sysListPanel;  
   }  
   
   protected FormListSort sortMethod()  
   {  
     return FormListSort::NoSort;  
   }  
   
 }  
3) Create l'estensione della VendParameters aggiungendo un nuovo tab. Il nuovo tab và laciato vuoto perchè sarà poi gestito dalla classe che abbiamo creato al passo 2


4)Fare la CoC sulla form VendParameters:

 [ExtensionOf(formStr(VendParameters))]  
 final class LILVendParameters_Form_Extension  
 {  
   public LILSysListPanel_VendDlvModeAssociation    sysListPanelDlvMode;  
   
   public LILSysListPanel_VendDlvModeAssociation parmSysListPanel(LILSysListPanel_VendDlvModeAssociation _sysListPanelDlvMode = sysListPanelDlvMode)  
   {  
     sysListPanelDlvMode = _sysListPanelDlvMode;  
     return sysListPanelDlvMode;  
   }  
   
   public void init()  
   {  
     next init();  
   
     this.initListPanels();  
   }  
   
   protected void initListPanels()  
   {  
     sysListPanelDlvMode.init();  
     sysListPanelDlvMode.fill();  
   }  
   
 }  

NB: Se stiamo aggiunendo il controllo in extension come in questo caso, come riportato quì:

L'inizializzazione della classe LILSysListPanel_VendDlvModeAssociation và fatta sottoscrivendo l'evento "Initializing" altrimenti il controllo non viene visualizzato correttamente nella form. Questo vale anche per "SysListPanelRelationTableCallback" usato in precedenza. Si veda come esempio la classe standard  SimulationJournalGroupForm_ApplicationSuite_Extension

5) Creiamo la classe EventHandler:

 class LILVendParametersFormEH  
 {  
   [FormEventHandler(formStr(VendParameters), FormEventType::Initializing)]  
   public static void VendParameters_OnInitializing(xFormRun sender, FormEventArgs e)  
   {  
     FormRun fr = sender as FormRun;  
     LILSysListPanel_VendDlvModeAssociation sysListPanelDlvMode = LILSysListPanel_VendDlvModeAssociation::newForm(fr, fr.controlId(formControlStr(VendParameters, LILDlvMode)));  
          
     fr.parmSysListPanel(sysListPanelDlvMode);  
   }  
 }  

mercoledì 27 settembre 2023

D365FFO - Messaggio di errore: "Stopped (error): X++ Exception: The text associated with this work item cannot be found in the assignee’s language"


Eseguire i seguenti passaggi:

  1. Richiamare il Workflow;
  2. Andare alla versione attiva del flusso e controllare che la configurazione del flusso di lavoro disponga delle traduzioni nelle lingue utilizzate dagli utenti come lingue predefinite;
  3. Controllare che la lingua predefinita degli utenti del flusso sia la stessa in uso dal workflow.


D365FFO - Financial Tag

 

La feature Financial Tag, attivabile dalla Gestione funzionalità dà la possibilità di definire fino a 20 tag definiti dall’utente, da utilizzare nei giornali (Giornale generale e Giornale generale globale).

Caratteristiche ed utilizzo dei Finacial Tag:

  • I tag non fanno parte della struttura dei conti;
  • I valori dei tag non vengono convalidati durante l’immissione o la registrazione;
  • I valori dei tag predefiniti non vengono immessi dai dati anagrafici;
  • I valori dei tag non sono inclusi nei set di dimensioni, in D365 non è possibile generare un bilancio di verifica per visualizzare i saldi per i valori dei tag. I valori dei tag sono visualizzati come dettaglio delle transazioni;
  • I tag dovrebbero essere utilizzati per tenere traccia di valori non riutilizzabili (numeri di documi o numeri di riferimento);
  • Possono essere attivi o disattivati in qualsiasi momento;
  • Non possono essere eliminati;
  • I valori dei tag vengono utilizzati solo per l’analisi e l’elaborazione interna;
  • I tag vengono impostati a livello di persona giuridica, possono essere condivisi utilizzando la funzione Dati condivisi.

1 Set up


1.1 Impostazione delimitatore dei Financial Tag


In Contabilità generale à Impostazione contabilità generale à Parametri di contabilità generale, selezionare il Tab Tag finanziari e definire il Delimitatore segmento di tag finanziari. Il delimitore non deve essere utilizzato in nessun valore di tag immesso nelle transazioni e non può essere modificato dopo essere stato definito.



       1.2 Creazione Financial Tag

In Contabilità generale à Piano dei conti à Tag finanziari à Tag finanziari, selezionare Nuovo e creare un Tag finanziario.

  •          Inserire l’etichetta (non sono ammessi gli spazi ed i caratteri speciali);
  •         Nel campo Tipo valore selezionare Testo, Elenco o Elenco personalizzato;
  •         Se si è selezionato Elenco nel campo Tipo valore, selezionare l’origine valore nel campo           Utilizza valori da. Il campo contiene un elenco di entità da cui è possibile seleziona i valori       dei tag durante l’immissione della transazione.






Selezionare il Tab Attiva o disattiva tag per attivare il tag finanziario creato e poi selezionare OK.




       2 Inserimento Financial Tag nelle transazioni


Quando si immettono le registrazioni, è possibile definire i valori dei Tag nell’intestazione delle registrazioni. Tali valori verranno utilizza come valori predefiniti per le righe del giornale. Come per gli altri valori predefiniti nel giornale, verranno automaticamente inseriti nelle nuove righe aggiunte al giornale.




È possibile immettere il Tag finanziario direttamente sulla singola riga del giornale.




       2.1 Esempi di utilizzo

  • Stock: tenere traccia di tutte le transazioni per un’unità specifica tutta la vita di un bene;
  • Ratei fornitore: tenere traccia delle spese maturate per fornitore nella contabilità generare (inserendo l’ID fornitore per le spese provenienti dalle voci di competenza);
  • Lotto: tracciamento delle spese per lotti specifici, valore aggiunto, ecc. passando attraverso la contabilità generale;
  • Campagne di marketing: tenere traccia delle spese in base alla campagna di marketing.

martedì 26 settembre 2023

D365FFO - Creare campi calcolati su una vista

In questo post vediamo come creare campi calcolati su una vista.Come esempio creiamo una vista basata su VendTable che mostra come colonne l'account num e le dimensioni finzanziarie di default "Cost center" e "Business unit". Questa possibilità è stata introdotta con AX 2012 e potenziata con la 365. In Ax 2009 invece non è possibile aggiungere metodi alle viste.

1) Creare una nuova view che chiameremo LILVendTableDefaultDimension

2) Aggiungere come datasource VendTable

3) Aggiungere AccounNum come field

4) Aggiungere due nuovi campi di tipo string così:


        che chiameremo CostCenter e BU

5) Aggiungere due nuovi metodi alla vista, che chiameremo  getCostCenterDisplayValue e getBUDisplayValue,per recuperare il display value della dimensione CostCenter e Business unit

 public static str getCostCenterDisplayValue()  
   {  
     str VendTableDefaultDimension = SysComputedColumn::returnField(tableStr(LILVendTableDefaultDimension)  
                                     ,dataEntityDataSourceStr(LILVendTableDefaultDimension,VendTable)  
                                     ,fieldStr(VendTable, DefaultDimension));  
   
     str backingEntityType = int2Str(tableNum(DimAttributeOMCostCenter));  
   
   
     return strFmt(@"select  
                 top 1 DimensionAttributeValueSetItemView.DISPLAYVALUE  
             from  
                 DimensionAttributeValueSetItemView  
             join  
                 DIMENSIONATTRIBUTEVALUE  
             on  
                 DIMENSIONATTRIBUTEVALUE.RECID = DimensionAttributeValueSetItemView.DIMENSIONATTRIBUTEVALUE  
             join  
                 DIMENSIONATTRIBUTE  
             on  
                 DIMENSIONATTRIBUTE.RECID = DimensionAttributeValueSetItemView.DIMENSIONATTRIBUTE  
             where  
                 DIMENSIONATTRIBUTEVALUESET = %1  
             and   BACKINGENTITYTYPE     = %2",VendTableDefaultDimension  
                                 ,backingEntityType);  
   }  
 public static str getBUDisplayValue()  
   {  
     str VendTableDefaultDimension = SysComputedColumn::returnField(tableStr(LILVendTableDefaultDimension)  
                                     ,dataEntityDataSourceStr(LILVendTableDefaultDimension,VendTable)  
                                     ,fieldStr(VendTable, DefaultDimension));  
   
     str dimensionName = 'BusinessUnit';  
   
   
     return strFmt(@"select  
                 top 1 DimensionAttributeValueSetItemView.DISPLAYVALUE  
             from  
                 DimensionAttributeValueSetItemView  
             join  
                 DIMENSIONATTRIBUTEVALUE  
             on  
                 DIMENSIONATTRIBUTEVALUE.RECID = DimensionAttributeValueSetItemView.DIMENSIONATTRIBUTEVALUE  
             join  
                 DIMENSIONATTRIBUTE  
             on  
                 DIMENSIONATTRIBUTE.RECID = DimensionAttributeValueSetItemView.DIMENSIONATTRIBUTE  
             where  
                 DIMENSIONATTRIBUTEVALUESET = %1  
             and   DIMENSIONATTRIBUTE.NAME  = '%2'",VendTableDefaultDimension  
                                  ,dimensionName);  
   }  
N.B: se si usano le stringhe nei parametri della where ( per esempio dimensionattribute.name =...) vanno messi tra doppi apici ''

6) Compilare le proprietà ViewMethod dei due campi coi nomi dei metodi appena creati

7) Buildare e sincronizzare



In AX 2012 la firma del metodo deve contenere la parola "server":

 public static server str getBUDisplayValue()  
Inoltre la funzione 

dataEntityDataSourceStr

Non è disponibile è và sostituita con la stringa del nome del datasource nella view

giovedì 10 agosto 2023

How to extend the Dialog class in D365 FFO

How to extend the standard class DIALOG in order to use in different points the custom methods(like lookup, validation etc).
It can be useful to use methods and standardize the code.

class MyCustomDialog extends Dialog

  public void my_lookupCustom(FormControl _callingControl )
  {
  // some code
  }
  boolean my_validateCustom(FormControl _callingControl)
  {
    // some code
  }
  boolean my_CustomGenericMethod(FormControl _callingControl)
  {
     // some code
  }
}

How to use the custom methods:

MyCustomDialog dialog; // use my custom class

dialog = new MyCustomDialog("TEXT");
dialog.addText("Some text");
dialog.my_CustomGenericMethod(....) ; 

Another way to use the custom methods:

// other example
 fieldSite.registerOverrideMethod(methodStr(FormStringControl, validate),
                                  methodStr(MyCustomDialog , my_validateCustom), dialog); 

Below how appear:




We can create different methods to meet our needs.

enjoy

sabato 24 giugno 2023

D365FO - KIT management

 In D365Fo there is a standard embedded kit management. It is reachable at the following menu Retail and Commerce.


Three functions are available in that place:

-    Product kits

-    Released product kits

   Kit orders


The first function allows the generation of a product having the flag kit activated as reported below.


Clicking on the button Configure in the button group Product kit, it's possible to define all the components the kit is made of.


Once the kit is approved, it will automatically generate a new variant related to the product master, usable everywhere in the system (it is then visible in the menu Released product kits).

For producing instead a Kit it is required to access the Kit order menu previously mentioned.


Once the warehouse is defined and the quantities to be produced, clicking on the button Post the system will consume the components and produce the kitted product (this function will use a BOMJournal to perform this activity).


venerdì 16 giugno 2023

How to find all D365 Security ROLES inheritance relations?

 

Is just a starting point

How can we catch all security relations OnPrem/Cloud environment? (also considering changes made by Security Configuration in Security Module)

It can be useful to know all the objects that have a relationship to the security role.

  • First a quicky recap:

The security AX2012/D365 consists of in three main elements:

The security inheritance – relations is like belog

The common security role tree is like below

A common issue or request is to find all relations about objects, to better understand how role work

-         which are the related duties?

-         which are the related privileges?

-        I need to know all security role dependency

....and others...

By SQL script we can find all security object references.

Table SECURITYOBJECTCHILDREREFERENCES can help us.

The main tables:

SECURITYPRIVILEGE

SECURITYDUTY

SECURITYROLES

SECURITYOBJECTCHILDREREFERENCES

Also the table SECURITYOBJECTCHILDREREFERENCES can be used for others query/relations

Run SQL scripts:

 -- All Privileges

select * from SECURITYPRIVILEGE

-- All Duty

select * from SECURITYDUTY

-- All Roles

select * from SECURITYROLES


HOW TO GET ALL SECURITY REFERENCES

  1. Get the list of all security roles with its duties

SELECT T2.Name as SecurityRole, T3.NAME as Duty
FROM SECURITYOBJECTCHILDREREFERENCES T1
JOIN SECURITYROLE T2 ON T1.IDENTIFIER = T2.AOTNAME
JOIN SECURITYDUTY T3 ON T1.CHILDIDENTIFIER = T3.IDENTIFIER
WHERE T1.OBJECTTYPE = 0 AND T1.CHILDOBJECTTYPE = 1

2. Get the list of all security roles with its privileges

SELECT T2.Name as SecurityRole, T3.NAME as Privilege
FROM SECURITYOBJECTCHILDREREFERENCES T1
JOIN SECURITYROLE T2 ON T1.IDENTIFIER = T2.AOTNAME
JOIN SECURITYPRIVILEGE T3 ON T1.CHILDIDENTIFIER = T3.IDENTIFIER
HERE T1.OBJECTTYPE = 0 AND T1.CHILDOBJECTTYPE = 2 

3. Get the list of all role-duty combination with privilege 

SELECT T2.Name as SecurityRole, T2.AOTNAME as RoleSystemName,  T3.NAME AS Duty, T3.IDENTIFIER as DutySystemName, T5.NAME as Privilege, T5.IDENTIFIER as PrivilegeSystemNam
FROM SECURITYOBJECTCHILDREREFERENCES T1
JOIN SECURITYROLE T2 ON T1.IDENTIFIER = T2.AOTNAME
JOIN SECURITYDUTY T3 ON T1.CHILDIDENTIFIER = T3.IDENTIFIER
JOIN SECURITYOBJECTCHILDREREFERENCES T4 on T4.IDENTIFIER = T3.IDENTIFIER
JOIN SECURITYPRIVILEGE T5 on T4.CHILDIDENTIFIER = T5.IDENTIFIER
WHERE T1.OBJECTTYPE = 0 AND T1.CHILDOBJECTTYPE = 1
AND T4.OBJECTTYPE = 1 AND T4.CHILDOBJECTTYPE = 2

Examples:

Duties related to role "SystemUser"

Privileges related to role "SystemUser" (by direct relation)

Privileges related to Role through Duties

enjoy