martedì 29 novembre 2016

AX 7 - Aggiungere una nuova number sequence

In questo post andiamo ad aggiungere una nuova sequenza numerica in "Parametri contabilità fornitori" che nel mio caso è servita per la numerazione automatica degli ordini di reso. La differenza principale rispetto ad AX 2012 è che il metodo  loadModule() della calsse NumberSeqModuleVendor è protetto e non possiamo quindi agganciarci tramite eventi. Dobbiamo quindi

1)Creare una nuova classe  che nel nostro caso si chiamerà FITNumberSeqModuleVendor_ReturnOrder che estende NumberSeqApplicationModule

2) Fare l'override del metodo loadModule aggiungendo i parametri della sequenza

     NumberSeqDatatype datatype = NumberSeqDatatype::construct();

     datatype.parmDatatypeId(extendedTypeNum(FITPackingSlipIdReturn));  
     datatype.parmDatatypeSameAsId(extendedTypeNum(FITPackingSlipIdReturn));  
     datatype.parmReferenceLabel(literalStr("Packing slip id return item"));  
     datatype.parmReferenceHelp(literalStr("Packing slip id return item"));  
     datatype.parmWizardIsContinuous(true);  
     datatype.parmWizardIsManual(NoYes::No);  
     datatype.parmWizardIsChangeDownAllowed(NoYes::No);  
     datatype.parmWizardIsChangeUpAllowed(NoYes::No);  
     datatype.parmWizardLowest(1);  
     datatype.parmWizardHighest(999999);  
     datatype.parmIsGroupEnabled(NoYes::Yes);  
     datatype.parmSortField(41);  
     datatype.addParameterType(NumberSeqParameterType::DataArea, true, false);  
     this.create(datatype);  

3)Fare l'override del metodo inizializa reference:

   public void initializeReference(NumberSequenceReference _reference, NumberSeqDatatype _datatype, NumberSeqScope _scope)  
   {  
     super(_reference, _datatype, _scope);  
     _reference.AllowSameAs = true;  
   }  

4)Fare l'override del metodo numberSequenceModule:

   public NumberSeqModule numberSeqModule()  
   {  
     return NumberSeqModule::Vend;  
   }  

5)Sottorscivere il delegato buildModulesMapDelegate della classe NumberSeqGlobal



   [SubscribesTo(classstr(NumberSeqGlobal),delegatestr(NumberSeqGlobal,buildModulesMapDelegate))]  
   static void buildModulesMapSubsciber(Map numberSeqModuleNamesMap)  
   {  
     NumberSeqGlobal::addModuleToMap(classnum(FITNumberSeqModuleVendor_ReturnOrder), numberSeqModuleNamesMap);  
   }  

6) Creare un job (Runnable) che fà il load:

 FITNumberSeqModuleVendor_ReturnOrder FITNumberSeqModuleVendor_ReturnrOder = new FITNumberSeqModuleVendor_ReturnOrder();  
 FITNumberSeqModuleVendor_ReturnOrder.load();  

7)Andare in Organization Administration -> CommonForms -> Numbersequences -> Numbersequences -> Generate, Seguire il wizard fino alla fine.

8)Verificare nei parametri della contabilità fornitori la presenza della nuova sequenza


martedì 18 ottobre 2016

AX 2012 - Gestione del marking via X++

Il codice sottostante serve per mettere in marking due transazioni:

 public void markTransactions(InventTransId _inventTransId,InventTransId _refInventTransId,InventDim _inventDim,InventQty _qtyTomark)  
 {  
      InventTransOriginId     inventTransOriginId     = InventTransOrigin::findByInventTransId(_inventTransId).RecId;  
      InventTransOriginId     refInventTransOriginId   = InventTransOrigin::findByInventTransId(_refInventTransId).RecId;  

      InventTransOrigin::updateMarking(inventTransOriginId,  
                          refInventTransOriginId,  
                          _qtyTomark);  

      InventTransOrigin::updateMarking(refInventTransOriginId,  
                          inventTransOriginId,  
                          -_qtyTomark,//il marking è possibile solo tra qty di segno opposto  
                          "",  
                          SortOrder::Ascending,  
                          false,  
                          _inventDim);  
 }  

venerdì 15 luglio 2016

AX 7 - Gestione Eventi

Da questo link una panoramica sulla gestione degli eventi:

- eventi del form
- eventi del datasource di un form
- eventi di un controllo specifico di un form


Li riporto come dal sito segnalato:

Form datasource from xFormRun

[FormEventHandler(formStr(SomeForm), FormEventType::Initialized)]
public static void SomeForm_OnInitialized(xFormRun sender, FormEventArgs e)
{
    FormDataSource MyRandomTable_ds = sender.dataSource(formDataSourceStr(SomeForm, MyRandomTableDS));
    ...
}

Get FormRun from form datasource

[FormDataSourceEventHandler(formDataSourceStr(MyForm, MyRandomTableDS), FormDataSourceEventType::Written)]
public static void MyRandomTableDS_OnWritten(FormDataSource sender, FormDataSourceEventArgs e)
{
    FormRun formRun = sender.formRun() as FormRun;
    // you can even call custom methods (I think IntelliSense won't work though)
    formRun.myCustomMethod();
}

Get FormRun from form control

[FormControlEventHandler(formControlStr(MyForm, MyButton), FormControlEventType::Clicked)]
public static void MyButton_OnClicked(FormControl sender, FormControlEventArgs e)
{
   FormRun formRun = sender.formRun() as FormRun;
   formRun.myCustomMethod();
}

Access form control from xFormRun

[FormEventHandler(formStr(SomeForm), FormEventType::Initialized)]
public static void SomeForm_OnInitialized(xFormRun sender, FormEventArgs e)
{
    // set the control to invisible as an example
    sender.design().controlName(formControlStr(SomeForm, MyControl)).visible(false);
}

Get current record in form control event

[FormControlEventHandler(formControlStr(SomeForm, SomeButton), FormControlEventType::Clicked)]
public static void SomeButton_OnClicked(FormControl sender, FormControlEventArgs e)
{
    // as an example the datasource number is used for access; I perceive the formDataSourceStr as more robust
    SomeTable callerRec = sender.formRun().dataSource(1).cursor();
}

Convert Common and use DataEventArgs

[DataEventHandler(tableStr(AnyTable), DataEventType::ValidatedWrite)]
public static void InventLocation_onValidatedWrite(Common sender, DataEventArgs e)
{
    // convert Common to AnyTable
    AnyTable anyTable = sender;
    // the DataEventArgs actually are ValidateEventArgs and can be converted
    ValidateEventArgs validateEventArgs = e;
    // the ValidateEventArgs carry the validation result (so far)
    boolean ret = validateEventArgs.parmValidateResult();
    // the table has some additional validation logic and gives back the result
    ret = anyTable.doSomeAdditionalCustomValidation(ret);
    // provide the args with the validation result
    validateEventArgs.parmValidateResult(ret);
}

Use the onValidatedFieldValue event properly

[DataEventHandler(tableStr(SomeTable), DataEventType::ValidatedFieldValue)]
public static void SomeTable_onValidatedFieldValue(Common sender, DataEventArgs e)
{
    SomeTable someTable = sender;
    // the clue is to know that the DataEventArgs actually are ValidateFieldValueEventArgs and that you can get the field name from them
    ValidateFieldValueEventArgs validateEventArgs = e;
    boolean ret = validateEventArgs.parmValidateResult();
    FieldName fieldName = validateEventArgs.parmFieldName();
    switch (fieldName)
    {
        case fieldStr(SomeTable, SomeCustomField):
            ... do some magic
            break;
    }
    validateEventArgs.parmValidateResult(ret);
}

Use the MappedEntityToDataSource event

[DataEventHandler(tableStr(MyTableEntity), DataEventType::MappedEntityToDataSource)]
public static void MyTableEntity_onMappedEntityToDataSource(Common _sender, DataEventArgs _eventArgs)
{
    DataEntityContextEventArgs eventArgs = _eventArgs;
    MyTableEntity entity = _sender;
    if (eventArgs.parmEntityDataSourceContext().name() == dataEntityDataSourceStr(MyTableEntity, MyTable))
    {
        MyTable myTable = eventArgs.parmEntityDataSourceContext().getBuffer();
        ... do some magic with it
    }
}

lunedì 6 giugno 2016

AX 2012 - Export su Excel, scelta del client da utilizzare in RDP


In questo articolo vi presento un'opzione a mio avviso molto utile e che permette di risolvere i problemi di licenze Excel che potrebbero avere alcune aziende.
Nelle opzioni utente è possibile specificare quale Excel aprire, nel caso di export in Excel di una tabella mentre si è collegati in desktop remoto.
  • Andare in "Strumenti > Opzioni" ed posizionarsi sul tab 'Generale'.
  • Scorrere verso il basso fino alla sezione 'Varie'
  • Impostare il parametro 'Remote Desktop session exports to'


Il valore di default è 'Client Excel' ma è possibile sceglier anche 'Server Excel'. Nel primo caso i dati vengono esportati nell'Excel della macchina locale dell'utente, nel secondo viene aperto il programma sul server in cui ci si è collegati in RDP.

Nota: Dalle mie prove il parametro funziona bene se si utilizza il 'Remote Desktop Connection' di Windows, in caso di utilizzo di programmi per la gestione delle connessioni non sempre il comportamento è quello voluto.

mercoledì 13 aprile 2016

giovedì 18 febbraio 2016

AX 2012 - Creazione dimensioni finanziare tramite pattern di valori

Nell'eventualità vi fosse richiesto di creare delle righe di coge (LedgerJournalTrans) da codice, è possibile che dobbiate assegnare manualmente i conti delle dimensioni finanziarie rispetto alla dimensione default. Se i conti sono più di uno è conveniente effettuare un merge e ve la caverete con poche righe di codice.

Probabilmente avrete quindi a disposizione i nomi (DisplayValue--->EDT: Name) e i valori (EDT: DimensionValue).

Non dovete fare altro che crearvi un container avente questa struttura (pattern):

  [numero_delle_dimensioni_da_fondere, "DisplayValue1", "DimensionValue1", "DisplayValue2", "DimensionValue2",.... , "DisplayValueN", "DimensionValueN"]  

con il container appena creato preoccupatevi di avere una DefaultDimension iniziale (ad esempio se avete la LedgerDimension) e poi crearne una nuova con il pattern sopra descritto:

 DimensionDefault        dimDefault, newDimDefault;  
 dimDefault = DimensionStorage::getDefaultDimensionFromLedgerDimension(axLedgerJournalTrans.parmLedgerDimension());  
 newDimDefault= AxdDimensionUtil::getDimensionAttributeValueSetId(patternContainer);  

Dopodichè vi basterà assegnare alla DefaultDimension il risultato del metodo serviceMergeDefaultDimensions passandogli la nuova e la vecchia dimensione :

 axLedgerJournalTrans.parmDefaultDimension(DimensionDefaultingService::serviceMergeDefaultDimensions(newDimDefault, dimDefault));  

NB; per questo esempio è stata utilizzata la classe AxLedgerJournalTrans anzichè la tabella.

martedì 19 gennaio 2016

AX 2009 / AX 2012 - Utilizzo della macro #InventDimJoin

Per effettuare la join con le dimensioni inventariali AX mette a disposizione una serie di macro per agevolare la scrittura della query:

 static void InventDimJoin(Args _args)  
 {  
   InventTrans   inventTrans;  
   InventDim    inventDim;  
   ItemId     itemId;  
   inventDim    inventDimCriteria;  
   InventDimParm  InventDimParm;  
   FromDate    fromDate;  
   ToDate     toDate;  
   ;  
   itemId  = "2-719-400-00";  
   fromDate = mkdate(1,9,2015);  
   toDate  = mkdate(30,9,2015);
  
   inventDimCriteria.InventLocationId = "EG";  
   InventDimParm.InventLocationIdFlag = NoYes::Yes;
   
   //oppure
   InventDimParm.initFromInventDim(inventDimCriteria); 

   select sum(Qty) from inventTrans  
   where inventTrans.ItemId   == itemId  
   && (inventTrans.StatusReceipt == StatusReceipt::Purchased  
   || inventTrans.StatusReceipt == StatusReceipt::Received)  
   && inventTrans.DatePhysical  >= fromDate  
   && inventTrans.DatePhysical  <= toDate  
   #InventDimJoin(inventTrans.inventDimId, InventDim, inventDimCriteria, InventDimParm, dimIdx);
  
   info(StrFmt("%1",inventTrans.Qty));  
 }