martedì 15 gennaio 2013

AX 2009 - Creare un form di lookup

Con questo post ho colto l'occasione per documentare una modifica abbastanza frequente ed utile, almeno per la mia esperienza. Ho ripreso un post già esistente (http://axaptapedia.com/Lookup_Form) che però faceva riferimento a più versioni di AX e che in parte era ormai obsoleto su AX2009. Con l'occasione ho tradotto il post in italiano e riadattato il contenuto al pieno funzionamento sulla versione AX2009, per le versioni precedenti rimando la lettura al post originale di cui ho esso il link.

Nell'esmpio sotto vedremo come creare una lookup per un'ipotetica tabella "Table" con campo chiave "Id". Il campo "Id" dovrà essere una stringa.
Come prima cosa dovremo creare un form molto semplice che conterrà nella maggior parte dei casi solo una griglia contenente i campi che vogliamo mostrare. Nella figura sotto è mostrato come impostare alcune proprietà del datasource:


Dovremo poi impostare come di seguito alcune proprietà nel design del form:


Assiamiamo che il nostro form di lookup abbia un control di tipo grid, e che in questo sia presente un control che punta al campo chiave che vogliamo utilizzare. Tale campo per comodità lo rinominiamo come "Table_Id" e impostiamo la proprietà 'AutoDeclaration' dello stesso a true.

Andremo adesso a modificare alcuni metodi del nostro form.

Override del metodo 'init' del form, in questo modo andremo ad impostare quel'è il controllo che sarà il risultato della nostra selezione.

 public void init()  
 {  
   ;  
   super();  
   element.selectMode(Table_Id);  
 }  

Override del metodo 'executeQuery' del datasource 'Table' del nostro form (ci permetterà di impostare il valore selezionato nel controllo chiamante come filtro della nostra form di lookup):

 public void executeQuery()  
 {  
   FormStringControl  callerControl  = SysTableLookup::getCallerStringControl(element.args());  
   ;  
   super();  
   Table_ds.findValue(fieldnum(Table,Id),callerControl.text());  
 }  

e del metodo 'init' del nostro dataSource (dove possiamo impostare la nostra query custom, definendo filtri, ordinamenti, ecc...):

 public void init()  
 {  
   Query        q = new Query();  
   QueryBuildDataSource qbds;  
   ;  
   super();  
   qbds = q.addDataSource(tablenum(Table));  
   qbds.orderMode(OrderMode::OrderBy);  
   qbds.addSortField(fieldNum(Table, some_other_field));  
   this.query(q);  
 }  

Se vogliamo inoltre rendere disponibili le wildcard (eg. "abc*" )per il filtro su stringhe, come concesso a standard da Ax, dovremo effettuare l'override del metodo 'run' del nostro form di lookup come segue:

 public void run()  
 {  
   FormStringControl  callerControl  = SysTableLookup::getCallerStringControl(element.args());  
   Boolean       filterLookup  = false;  
   ;  
   // if lookup was called with filter, then supress autoSearch  
   if (callerControl.text() && callerControl.hasChanged())  
   {  
     filterLookup = true;  
     Table_ds.autoSearch(false);  
   }  
   super();  
   // after call of super filter search manually by applying past filter  
   if (filterLookup)  
   {  
     Table_ds.research();  
     Table_ds.filter(fieldnum(Table, Id),callerControl.text());  
   }  
 }  

Se vogliamo che il form appena creato diventi il default per un determinato Extended Data Type dovremo semplicemente impostare il nome del nostro form nella proprietà 'FormHelp' del EDT.

Se, in alternativa, vogliamo utilizzare la nostra lookup in un controllo specifico, lasciando di default la lookup standard di AX, dovremo andar a customizzare il metodo lookup del nostro chiamante.
Definito per comodità il controllo chiamate come "CallerTable_CallerId" e impostata la proprietà 'AutoDeclaration' dello stesso a true, dovremo andare a modificare il metodo lookup del campo ad esso associato come segue. Nell'esempio la tabella chiamante sarà "CallerTable", il campo "CallerId" e datasource "CallerTable_DS".

 public void lookup(FormControl _formControl, str _filterStr)  
 {  
   Args args = new Args();  
   FormRun itemLookUp;  
   ;  
   args.name(formstr(CustomLookupForm));  
   args.caller(CallerTable_CallerId);  
   itemLookUp = new FormRun(args);  
   itemLookUp.init();  
   this.performFormLookup(itemLookUp, CallerTable_CallerId);  
 }  

 public void performFormLookup(FormRun _form, FormControl _formControl)  
 {  
   super(_form, _formControl);  
 }  

Avrete in questo modo un form di lookup custom perfettamente funzionante.

martedì 25 dicembre 2012

mercoledì 14 novembre 2012

Dimensioni inventariali - cambio magazzino

In questo breve post vedremo come manipolare le dimensioni inventariali. In particolare vedremo come cambiare il magazzino di un articolo da riga ordine cliente dal magazzino 'ML' al magazzino 'A'. Se apriamo la sales line, il magazino è indicato nel tab dimensione -> gruppo dimensioni inventariali -> magazzino


Per cambiare magazzino dobbiamo:

  1. Prendere il codice attuale della dimensione inventariale
  2. Modificare il magazzino
  3. Ricalcolare il nuovo codice (o cercarlo se già esiste)
  4. Riassegnare il nuovo codice alla riga d'ordine 
 static void InventLocationIdChange(Args _args)  
 {  
   InventDim  InventDimNew;  
   SalesLine  SalesLine;  
   ttsbegin;  
   select firstOnly forupdate SalesLine;  
   InventDimNew = SalesLine.inventDim();  
   InventDimNew.InventLocationId = 'A';  
   InventDimNew = InventDim::findOrCreate(InventDimNew);  
   SalesLine.InventDimId = InventDimNew.inventDimId;  
   SalesLine.update();  
   ttscommit;  
 }  

Se andiamo a riaprire la riga d'ordine, vediamo che il magazzino è stato modificato:


In modo analogo si possono cambiare tutti gli altri attributi delle dimensioni inventariali

martedì 13 novembre 2012

AX 2009 - Number Sequence, premessa e sviluppo modulo custom

Le sequenza numerica è uno strumento molto utilizzato in quanto permette la creazione automatica di valori alfanumerici, sequenziali ed unici per specifici campi, ogni volta che quest'ultimi vengono inizializzati.

È possibile specificare la continuità o meno nelle sequenze generate. È anche possibile specificare il formato del numero definendone il formato (vedi "loadModule").

Se vogliamo dare vita ad una NumberSequence completamente custom, la prima cosa da creare e definire è l'Extended Data Type per tale sequenza, definendo il tipo (stringa), lunghezza e label. Consiglio anche la creazione di un nuovo enumerato di tipo "NumberSeqModule" (es. ExampleEnum) .

La classe di riferimento per le sequenze numeriche è la NumberSequenceReference, ed essa è estesa dalle varie sottoclassi che rappresentano i vari moduli (NumberSequenceReference_Customer, NumberSequenceReference_Ledger ecc. ecc.).

Come secondo passo, per la creazione di un modulo custom, è quindi necessario definire una classe (NumberSequenceReference_Custom) che estenda quella di riferimento e contenga i metodi in override "loadModule" e "numberSeqModule".

  Class NumberSeqReference_Custom extends NumberSeqReference   
  {   
  }   

Nel primo si specificano le caratteristiche della sequenza, il secondo deve restituire l'enumerato NumberSeqModule creato in precedenza.

  protected void loadModule()   
  {   
   NumberSequenceReference numRef;   
   ;    
   numRef.DataTypeId       = typeId2ExtendedTypeId(typeid(EDTCreato));   
   numRef.ReferenceHelp     = "Etichetta di help";   
   numRef.WizardContinuous    = true;  
   numRef.WizardManual      = NoYes::No;   
   numRef.WizardAllowChangeDown = NoYes::No;   
   numRef.WizardAllowChangeUp  = NoYes::No;  
   numRef.wizardHighest     = 99999999;   
   numRef.SortField       = 1;   
   this.create(numRef);   
  }   

 public static NumberSeqModule numberSeqModule()  
 {  
   return NumberSeqModule::ExampleEnum;  
 }  

Inoltre,bisogna modificare i metodi construct() e moduleList() nella classe NumberSeqReference stessa per aggiungere i riferimenti alla nuova classe-modulo creata.


  static public container moduleList()    
  {    
   container moduleList;    
   ;    
   moduleList += NumberSeqReference_Bank::numberSeqModule();    
   moduleList += NumberSeqReference_BOM::numberSeqModule();    
   moduleList += NumberSeqReference_Customer::numberSeqModule();    
   moduleList += NumberSeqReference_Document::numberSeqModule();    
   moduleList += NumberSeqReference_ForeignTrade::numberSeqModule();    
   moduleList += NumberSeqReference_General::numberSeqModule();    
   moduleList += NumberSeqReference_Inventory::numberSeqModule();    
   moduleList += NumberSeqReference_Ledger::numberSeqModule();    
   moduleList += NumberSeqReference_Location::numberSeqModule();    
   moduleList += NumberSeqReference_MasterPlanning::numberSeqModule();    
   moduleList += NumberSeqReference_Production::numberSeqModule();    
   moduleList += NumberSeqReference_Project::numberSeqModule();    
   moduleList += NumberSeqReference_PurchaseOrder::numberSeqModule();    
   moduleList += NumberSeqReference_Route::numberSeqModule();    
   moduleList += NumberSeqReference_SalesOrder::numberSeqModule();    
   moduleList += NumberSeqReference_Tax::numberSeqModule();    
   moduleList += NumberSeqReference_Vendor::numberSeqModule();    
   moduleList += NumberSeqReference_Internet::numberSeqModule();    
   moduleList += NumberSeqReference_Asset::numberSeqModule();   
   //Riferimento alla nostra classe-modulo    
   moduleList += NumberSeqReference_Custom::numberSeqModule();  
   return moduleList;   
  }   

 public static numberSeqReference construct(NumberSeqModule _module)   
  {   
   switch (_module)   
   {   
    case (NumberSeqReference_Ledger::numberSeqModule())   : return new NumberSeqReference_Ledger(_module);   
    case (NumberSeqReference_Tax::numberSeqModule())   : return new NumberSeqReference_Tax(_module);   
    case (NumberSeqReference_Bank::numberSeqModule())   : return new NumberSeqReference_Bank(_module);   
    case (NumberSeqReference_SalesOrder::numberSeqModule())  : return new NumberSeqReference_SalesOrder(_module);   
    case (NumberSeqReference_ForeignTrade::numberSeqModule()) : return new NumberSeqReference_ForeignTrade(_module);   
    case (NumberSeqReference_Customer::numberSeqModule())  : return new NumberSeqReference_Customer(_module);   
    case (NumberSeqReference_PurchaseOrder::numberSeqModule()) : return new NumberSeqReference_PurchaseOrder(_module);   
    case (NumberSeqReference_Vendor::numberSeqModule())   : return new NumberSeqReference_Vendor(_module);   
    case (NumberSeqReference_Inventory::numberSeqModule())  : return new NumberSeqReference_Inventory(_module);   
    case (NumberSeqReference_BOM::numberSeqModule())   : return new NumberSeqReference_BOM(_module);   
    case (NumberSeqReference_Route::numberSeqModule())   : return new NumberSeqReference_Route(_module);   
    case (NumberSeqReference_Production::numberSeqModule())  : return new NumberSeqReference_Production(_module);   
    case (NumberSeqReference_MasterPlanning::numberSeqModule()) : return new NumberSeqReference_MasterPlanning(_module);   
    case (NumberSeqReference_Project::numberSeqModule())  : return new NumberSeqReference_Project(_module);   
    case (NumberSeqReference_Location::numberSeqModule())  : return new NumberSeqReference_Location(_module);   
    case (NumberSeqReference_Document::numberSeqModule())  : return new NumberSeqReference_Document(_module);   
    case (NumberSeqReference_General::numberSeqModule())  : return new NumberSeqReference_General(_module);   
    case (NumberSeqReference_Internet::numberSeqModule())  : return new NumberSeqReference_Internet(_module);   
    case (NumberSeqReference_Asset::numberSeqModule())   : return new NumberSeqReference_Asset(_module);   
    // Riferimento alla nostra classe-modulo  
    case (NumberSeqReference_Custom::numberSeqModule())     : return new NumberSeqReference_Custom(_module);   
  }   
   throw error(strFmt("@SYS53742"));   
  }  

Ora è necessario parametrizzare la NumberSequence e richiamarla quando occorre.

Innanzi tutto occorre creare una tabella parametrica e relativo form.
E' necessario poi definire all'interno della tabella i metodi numberSeqModule() e numberSeqReference():

 client server static NumberSeqModule numberSeqModule()  
 {  
   return NumberSeqReference_Custom::numberSeqModule();  
 }  

 client server static NumberSeqReference numberSeqReference()  
 {  
   return NumberSeqReference::construct(NumberSeqReference_Custom::numberSeqModule());  
 }  

Nel form, altresì, vanno inseriti come datasource sia la nuova tabella parametrica che la tabella NumberSequenceReference.
Ed inoltre:
- i metodi numberSeqPreInit(), numberSeqPostInit();
- il metodo executeQuery nel datasource NumberSequenceReference.

Dopo di che nel nostro form custom, potremo inserire i nostri riferimenti alla sequence creata, dopo aver definito codice, nome, minimo, massimo, formato ecc. nella tabella NumberSequenceTable (quest'ultima procedura si puo eseguire anche tramite wizard in Basic -> Setup -> NumberSequences -> Number Sequences).

Infine occorrerà solamente "staccare" di volta in volta la sequence. Manualmente si può fare così:

 ...    
   ExtendedTypeId id = TypeID2ExtendedTypeId(TypeId(EDTCreato));  
   NumberSeq num = NumberSeq::newGetNum(NumberSequenceReference::find(id));  
   str  sequence;  
   ;  
   num.used(); // segnalo la sequence come "in uso"
   sequence = num.num();  
 ...  

Ciclo sui record di un datasource

Molto spesso è utile dover elaborare dei record selezionati  in un form dall'utente, filtrati secondo filtri custum. Ovviamente tale informazione è contenuta nel datasource del fom. Ecco un esempio di codice che cicla i record del listino prezzi selezionati dall'utente ed aggiorna il campo FomDate con la data odierna:

 public void setToDate()  
 {  
   PriceDiscTable   PriceDiscTableOrig;  
   Common       common;  
   ;  
   ttsBegin;  
   for(common = PriceDiscTableDS.getFirst(true)?PriceDiscTableDS.getFirst(true):PriceDiscTableDS.cursor();common; common = PriceDiscTableDS.getNext())  
   {  
     PriceDiscTableOrig = PriceDiscTable::findRecId(common.RecId, true);  
     PriceDiscTableOrig.ToDate = today() ;  
     PriceDiscTableOrig.update();  
   }  
   ttsCommit;  
 }  


Importante la chiamata al metodo find per ottenere il buffer, altrimenti continueremmo a lavorare a livello di datasource perdendo le modifiche. Quando la procedura termina si deve chiamare il refresh del datasource per vedere le modifiche.

venerdì 28 settembre 2012

AX 2009 - Ordinare in modo decrescente le righe di un form

Ax in automatico ordina le righe della grid di un form in modo crescente. Se vogliamo cambiare l'ordine in decrescente, la procedura è semplice. Basta modificare il metodo "init" nel datasource della form in cui ci sono i dati della grid.


 public void init()  
 {  
   super();  
   this.query().dataSourceTable(tableNum(SalesTable)).addSortField(fieldNum(SalesTable, RecId),SortOrder::Descending);  
 }  

AX 2009 - Lookup con lista dei campi di tabella

Vi serve visualizzare una lista di tutti i campi di una determinata tabella? Ecco il codice da implementare nel metodo "lookup" (override sul campo del datasource in cui vogliamo visualizzarla).

 public void lookup(FormControl _ctrl, str _filterStr)  
 {  
   SysTableLookup       sysTableLookup = SysTableLookup::newParameters(Tablenum(Utilelements),_ctrl);  
   Treenode             formnode;  
   Query                query = new Query();  
   QueryBuildDataSource queryBuildDataSource;  
   Treenode             datasource;  
   TableName            tableName;  
   TableId              tableId;  
   ;  
    
   formnode = treenode::findNode(@'\forms\SalesTable\data sources'); //form dove è contenuto il datasource in questo caso SalesTable
   
   datasource = formnode.AOTfirstChild();  
   tableName = datasource.AOTgetProperty('Table');  
   if (datasource)  
   {  
     tableId = tablename2id(tableNam);//id di SalesTable  
     sysTableLookup.addLookupfield(fieldnum(UtilElements, Name));  
     queryBuildDataSource = query.addDataSource(tablenum(UtilElements));  
     queryBuildDataSource.addRange(fieldnum(utilelements,recordtype)).value(queryvalue(UtilelementType::TableField));//range sul campo RecordType, dove specifichiamo che vogliamo i campi di una tabella
     queryBuildDataSource.addRange(fieldnum(Utilelements,ParentId)).value(queryvalue(tableId));  // range sul ParentId dei campi, gli specifichiamo quello di SalesTable
     sysTableLookup.parmQuery(query);  
     sysTableLookup.performFormLookup();  
   }  
 }  
A presto il lookup che visualizza la lista delle tabelle!