mercoledì 9 agosto 2017

AX 2009 - Creazione di un ordine pianificato in X++

Questo metodo aggiunto alla tabella ReqPO consente di creare un ordine pianificato via codice.
Ho preso come riferimento quello che lo standard fà quì:

\Classes\ReqCalc\covCreatePlannedOrder()

 server void CONcreatePlannedOrderFromJFlex(  
   InventLocationId  _fromInventLocationId  = '',  
   InventLocationId  _toInventLocationId   = ''  
   )  
 {  
   ReqCalcExplodePo  reqCalcExplodePo;  
   ReqTrans      reqTransPo;  
   ReqTrans      transferDemand;  
   InventDim      inventDimDemand;  
   ReqPlanData     reqPlanData;  
   InventDim      inventDim;  
   inventSite     inventSite;  
   InventDimGroupId  dimGroupId;  
   ;  
   inventDim.InventSiteId = (select firstonly SiteId from inventSite).siteId;  
   inventDim = InventDim::findOrCreate(inventDim);  
   this.ReqPlanId     = cONJFLEXParameters.ReqPlanId;  
   this.RefType      = ReqPO::reqPoType2ReqRefType(ReqPoType::Purch);  
   this.ReqDateDlv     = today();  
   this.initFromItemId("lillilor");  
   this.Qty        = 10;  
   this.VendId       = "lillilor";  
   dimGroupId = InventTable::find("lillilor").DimGroupId;  
   inventDim.clearNotCovPrDim(dimGroupId);  
   if (! this.validateWrite())  
     throw Exception::Error;  
   ttsbegin;  
   this.initPurchQty();  
   reqPlanData = ReqPlanData::newReqPlanId(this.ReqPlanId);  
   this.RefId = NumberSeq::newGetNumFromCode(reqPlanData.sequencePlannedOrder()).num();  
   this.CovInventDimId = InventDim::findOrCreate(inventDim).InventDimId;  
   /*  
     Must BOM and Route be created  
   */  
   if (this.RefType == ReqRefType::BOMPlannedOrder)  
   {  
     if (reqPlanData.mustTransBeExploded(this.reqSetupDim(),this))  
       this.setBOMCreated( this.ItemBomId  ? true : false);  
     if (reqPlanData.mustTransBeScheduled(this.reqSetupDim(),this))  
       this.setRouteCreated(this.ItemRouteId ? true : false);  
   }  
   this.insert();  
   reqTransPo.ReqDateDlvOrig   = this.ReqDateDlv;  
   reqTransPo.Level       = this.reqSetup().itemLevel();  
   reqTransPo.insertFromReqPo(this,false,reqPlanData);  
   if (this.RefType == ReqRefType::BOMPlannedOrder || this.RefType == ReqRefType::TransferPlannedOrder)  
   {  
     if (this.RefType == ReqRefType::TransferPlannedOrder)  
     {  
       transferDemand = reqTransPo.reqTransTransferDemand(true);  
       if (transferDemand.RecId)  
       {  
         inventDimDemand = transferDemand.inventDim();  
         inventDimDemand.InventLocationId  = _fromInventLocationId;  
         inventDimDemand.initFromInventLocation(inventDimDemand.inventLocation(), dimGroupId);  
         transferDemand.CovInventDimId    = InventDim::findOrCreate(inventDimDemand).InventDimId;  
         transferDemand.update();  
       }  
     }  
     reqCalcExplodePo = ReqCalcExplode::newReqTrans(reqTransPo,null);  
     reqCalcExplodePo.run();  
     reqCalcExplodePo.refreshUpdatedReqPo(this);  
   }  
   ttscommit;  
 }  

Riferimeti:

https://community.dynamics.com/ax/f/33/t/136809

mercoledì 22 febbraio 2017

AX 2012 - D365FFO - Convertire date e numeri in base alla Culture

Ciao in questo articolo posto 2 piccoli metodi che ho scritto per convertire date e numeri in base alla lingua passata come parametro:

Questo per le date:

 public static str Date2StringByLanguage(TransDate _transDate, LanguageId _languageId)
   {
     str                   timeFormat;
     System.DateTime             thisDate;
     System.Globalization.CultureInfo    cultInfo;
     System.Globalization.DateTimeFormatInfo formatInfo;
     ;
     thisDate = new System.DateTime( year(_transDate),
                   mthOfYr(_transDate),
                   dayOfMth(_transDate));
     cultInfo = System.Globalization.CultureInfo::CreateSpecificCulture(_languageId);
     formatInfo = cultInfo.get_DateTimeFormat();
     timeFormat = formatInfo.get_ShortDatePattern();
     return thisDate.ToString(timeFormat);
   }

Questo per i numeri:

 static str Num2StringByLanguage(real _num, LanguageId _languageId)
   {
     str                   numformat;
     System.Globalization.CultureInfo    cultInfo;
     System.Globalization.NumberFormatInfo  formatInfo;
     str                   NumberDecimalSeparator;
     str                   NumberGroupSeparator;
     int                   DecimalSeparator;
     int                   ThousandSeparator;
     cultInfo = System.Globalization.CultureInfo::CreateSpecificCulture(_languageId);
     formatInfo = cultInfo.get_NumberFormat();
     NumberDecimalSeparator = formatInfo.get_NumberDecimalSeparator();
     NumberGroupSeparator = formatInfo.get_NumberGroupSeparator();
     switch(NumberDecimalSeparator)
     {
       case ".":
         DecimalSeparator = 1;
         break;
       case ",":
         DecimalSeparator = 2;
         break;
       default:
         DecimalSeparator = -1;
         break;
     }
     switch(NumberGroupSeparator)
     {
       case ".":
         ThousandSeparator = 1;
         break;
       case ",":
         ThousandSeparator = 2;
         break;
       case " ":
         ThousandSeparator = 3;
         break;
       default:
         ThousandSeparator = -1;
         break;
     }
     return num2str(_num,-1,-1,DecimalSeparator,ThousandSeparator);
   }

lunedì 6 febbraio 2017

AX 2012 - Recuperare l'indirizzo del cliente per tipo

Con questo semplice job possiamo recuperare l'indirizzo del cliente ricercando per tipo (Fatturazione, spedizione etc..)

 static void getCustomerAddressType(Args _args)  
 {  
    DirPartyLocation            partyLocation;
    DirPartyLocationRole        partyLocationRole;
    LogisticsLocation           location;
    LogisticsLocationRole       locationRole;
    LogisticsPostalAddress      postalAddress;
    CustTable                   custTable;
    LogisticsLocationRoleType   type;

    custTable   = CustTable::find("CLI0046");
    type        = LogisticsLocationRoleType::Business;

    select firstonly postalAddress
        exists join location
        where location.RecId == postalAddress.Location
        exists join locationRole
        where locationRole.Type  == type
        exists join partyLocation
        where
        partyLocation.Location == location.RecId &&
        partyLocation.Party == custTable.party
        exists join partyLocationRole
        where partyLocationRole.PartyLocation == partyLocation.RecId &&
        partyLocationRole.LocationRole == locationRole.RecId;

    info(postalAddress.Address);
 }  


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.