lunedì 25 marzo 2013

AX 2012 - Importazione dati da file CSV / EXCEL

In questo post vediamo come importare dati da un file .CSV in AX, inserendo i dati in un tabella custom chiamata "MyTable" avente due campi "Field1" e "Field2" di tipo stringa. Il metodo sottostante effettua l'operazione leggendo i dati dal file C:\TEMP\test.csv

 static void CSVLoadData(Args _args)  
 {  
   TextIo             inFile;  
   container            line;  
   Counter             records;  
   SysOperationProgress      simpleProgress;  
   container            fileContainer;  
   Counter             loopCounter;  
   Mytable             Mytable;  
   CustTable            CustTable;  
   InventTable           InventTable;  
   str               filename;  
   #OCCRetryCount  
   #AviFiles  
   #File 
   
   if (curExt() != 'DAT')
   {
		throw error("This script must run in the DAT company!");
   } 
   
   filename = WinAPI::getOpenFileName(0,
                                   [WinAPI::fileType(#csv),#AllFilesName + #csv],
                                   @'C:\users\',
                                   "@SYS53008"
                                   );
                                   
   if(!filename)
   {
      return;
   }                               
                                   
   try  
   {  
     //Caricamento file per righe, ogni riga è memorizzata in un container  
     inFile = new TextIo(filename, 'r');  
     inFile.inRecordDelimiter('\n');  
     inFile.inFieldDelimiter(';');  
     while (inFile.status() == IO_Status::OK)  
     {  
       fileContainer += [infile.read()];  
     }  
     inFile = null;  
   }  
   catch  
   {  
     throw error(strFmt("@SYS18678", filename));  
   }  
   simpleProgress = SysOperationProgress::newGeneral(#aviUpdate, "Importazione...", conLen(fileContainer));  
   ttsBegin;  
   records = 0;  
   //il ciclo parte da 2 perchè si suppone che la prima riga sia di intestazione  
   for (loopCounter = 2; loopCounter <= conLen(fileContainer) - 1 ; loopCounter++)  
   {  
     Mytable.clear();  
     simpleProgress.incCount();  
     try  
     {  
       line = conPeek(fileContainer, loopCounter); // lettura della riga i-esima  
       Mytable.Field1       = conPeek(line, 1);  
       Mytable.Field2       = conPeek(line, 2);  
       Mytable.insert();  
       records++;  
       simpleprogress.setText(strfmt("@SYS76835", loopCounter, Mytable.RecId));  
     }  
     catch (Exception::Deadlock)  
     {  
       if (xSession::currentRetryCount() < #RetryNum)  
       {  
         retry;  
       }  
     }  
   }  
   ttsCommit;  
   info(strFmt("Inseriti %1 record", records));  
 }  

Per leggere i dati da EXCEL possiamo invece usare il seguente  job:

 static void EXCELLoadData(Args _args)  
 {  
   SysExcelApplication     application;  
   SysExcelWorkbooks      workbooks;  
   SysExcelWorkbook      workbook;  
   SysExcelWorksheets     worksheets;  
   SysExcelWorksheet      worksheet;  
   SysExcelCells        cells;  
   COMVariantType       type;  
   Name            name;  
   FileName          filename;  
   int             row;  
   container          TableDataContainer,  
                 line;  
   SysOperationProgress    simpleProgress;  
   Counter           BLrecords,ITrecords,ITRecordsNotFound;  
   Counter           loopCounter;  
   InventTable         inventTable;  
   ItemId           itemId;  
   boolean           previewMode = true; //EXECUTION MODE  
   #AviFiles  
   #File
   
    str COMVariant2Str(COMVariant _cv, int _decimals = 0, int _characters = 0, int _separator1 = 0, int _separator2 = 0)
    {
        switch (_cv.variantType())
        {
            case (COMVariantType::VT_BSTR):
                return _cv.bStr();

            case (COMVariantType::VT_R4):
                return num2str(_cv.float(),_characters,_decimals,_separator1,_separator2);

            case (COMVariantType::VT_R8):
                return num2str(_cv.double(),_characters,_decimals,_separator1,_separator2);

            case (COMVariantType::VT_DECIMAL):
                return num2str(_cv.decimal(),_characters,_decimals,_separator1,_separator2);

            case (COMVariantType::VT_DATE):
                return date2str(_cv.date(),123,-1,-1,-1,-1,-1);

            case (COMVariantType::VT_EMPTY):
                return "";

            default:
                throw error(strfmt("@SYS26908", _cv.variantType()));
        }
    return "";
    }
   
   ;  
     
   application = SysExcelApplication::construct();  
   workbooks = application.workbooks();  
   
   filename = WinAPI::getOpenFileName(0,  
                   [WinAPI::fileType(#xlsx),#AllFilesName + #xlsx],  
                   strFmt(@'C:\users\%1\Desktop',WinApi::getUserName()),  
                   "@SYS53008"  
                   );  
   try  
   {  
     workbooks.open(filename);  
   }  
   catch (Exception::Error)  
   {  
     application.quit();  
   
     throw error("@SYS19358");  
   }  
   
   workbook = workbooks.item(1);  
   worksheets = workbook.worksheets();  
   worksheet = worksheets.itemFromNum(1);  
   cells = worksheet.cells();  
   row = 1;  
   
   //read data  
   do  
   {  
     row++;  
       
     line = [cells.item(row, 1).value().bStr()
     		 ,COMVariant2Str(cells.item(row, 2).value())
             ,COMVariant2Str(cells.item(row, 10).value(),-1,-1,-1,-1)];
             
     TableDataContainer += [line];  
     type = cells.item(row+1, 1).value().variantType();  
   
   }  
   while (type != COMVariantType::VT_EMPTY);  
   
   application.quit();  
 }  

giovedì 14 marzo 2013

AX 2012 - Number Sequence Framework

In questo post vedremo il funzionamento delle sequenza numeriche in AX 2012 creando una nuova sequenza per un modulo custom chiamato FCM con la relativa tabella dei parametri. La fonte di questo articolo è:
http://msdn.microsoft.com/en-us/library/aa608474.aspx Rispetto all'originale ho approfondito alcuni aspetti che risultavano un pò vaghi.

1) Creazione EDT: creiamo un unovo EDT per la nostra sequenza, unica accortezza che il nostro EDT dovrè assere di tipo "String". Rinominiamo l'EDT in "NSid", impostiamo una lunghezza (per es 15) assegnamo la label "Test sequenza numerica" e salviamo.

2) Creazione Tabella parametri: La tabella dovrò contenere ALMENO il campo chiave con il nostro EDT appena creato. Chiamiamo la tabella "NSParameters" e aggiungiamoci il campo. Implementiamo poi il classico metodo "find()" e facciamo l'ovverride dei metodi delete() e update() rispettivamente:

 void delete()  
 {  
   throw error("@SYS23721");  
 }  

 void update()  
 {  
   super();  
   flush NSParameters;  
 }  

ed i metodi per la gestione della sequenza:

 client server static NumberSequenceReference numRefNSIdNum()  
 {  
   NumberSeqScope scope = NumberSeqScopeFactory::createDataAreaScope(curext());  
   return NumberSeqReference::findReference(extendedtypenum(NSId), scope);  
 }  

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

3)Modifica base enum: modificare l'enumerato "NumberSeqModule" aggiungendo il un nuovo valore che chiameremo FCM

4) Creazione nuova classe : La classe servirà a gestire la sequenza:

 //Classe per il setup delle sequenza numerica  
 class NumberSeqModuleFacilityManagement extends NumberSeqApplicationModule  
 {  
 }  

implementando i metodi:

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

 void loadModule()  
 {  
   NumberSeqDatatype datatype = NumberSeqDatatype::construct();  
   datatype.parmDatatypeId(extendedTypeNum(NSid));  
   datatype.parmReferenceHelp("Riferimento unico all'elemento della sequenza");  
   datatype.parmWizardIsContinuous(false);  
   datatype.parmWizardIsManual(NoYes::No);  
   datatype.parmWizardIsChangeDownAllowed(NoYes::No);  
   datatype.parmWizardIsChangeUpAllowed(NoYes::No);  
   datatype.parmSortField(1);  
   datatype.parmWizardHighest(999999);  
   datatype.addParameterType(NumberSeqParameterType::DataArea, true, false);  
   this.create(datatype);  
 }  

5) Creazione form: creiamo il form per la nostra tabella dei paramtri. Come suggeriscono le BP di miscosoft e bene crere un nuovo tab con la relativa griglia che conterrà la Sequenza per il nostro modulo. Al form dovrà anche essere aggiunto il datasource della tabella NumberSequenceReference con le seguenti proprietà:



il form dovrà contenere i seguenti metodi:

 public class FormRun extends ObjectRun  
 {  
   boolean          runExecuteDirect;  
   TmpIdRef          tmpIdRef;  
   NumberSeqScope       scope;  
   NumberSeqApplicationModule numberSeqApplicationModule;  
   container         numberSequenceModules;  
 }
 
 public void init()  
 {  
   this.numberSeqPreInit();  
   super();  
   this.numberSeqPostInit();  
 } 
 
 void numberSeqPostInit()  
 {  
   numberSequenceReference_ds.object(fieldNum(NumberSequenceReference, AllowSameAs)).visible(numberSeqApplicationModule.sameAsActive());  
   referenceSameAsLabel.visible(numberSeqApplicationModule.sameAsActive());  
 } 
 
 void numberSeqPreInit()  
 {  
   runExecuteDirect  = false;  
   numberSequenceModules = [NumberSeqModule::FCM, NumberSeqModule::FCM];  
   numberSeqApplicationModule = new NumberSeqModuleCustomer();  
   scope = NumberSeqScopeFactory::createDataAreaScope();  
   NumberSeqApplicationModule::createReferencesMulti(numberSequenceModules, scope);  
   tmpIdRef.setTmpData(NumberSequenceReference::configurationKeyTableMulti(numberSequenceModules));  
 }  

Implementiamo i seguenti metodi nel datasource  "NumberSequenceReference" :

 void removeFilter()  
 {  
   runExecuteDirect = false;  
   numbersequenceReference_ds.executeQuery();  
 }
 
 void executeQuery()  
 {  
   if (runExecuteDirect)  
   {  
     super();  
   }  
   else  
   {  
     runExecuteDirect = true;  
     this.queryRun(NumberSeqReference::buildQueryRunMulti(numberSequenceReference,  
                                tmpIdRef,  
                                numberSequenceTable,  
                                numberSequenceModules,  
                                scope));  
     numbersequenceReference_ds.research();  
   }  
 } 
 
 int active()  
 {  
   int ret;  
   ret = super();  
   buttonNumberSequenceGroup.enabled(numberSequenceReference.groupEnabled());  
   return ret;  
 }  

Nel nodo design , nella griglia relativa al datasource "NumberSequenceReference" aggiungiamo 6 controlli.
Possiamo direttamente copiare questi controlli  dal tab numberSeq del form "CustParameters":


5 nella griglia:
- referenceLabel
- NumberSequenceReference_NumberSequenceId
- taxBookSectionId
- NumberSequenceReference_AllowSameAs
- referenceSameAsLabel

1 fuori:
- referenceHelp

Consigliamo a questo punto un riavvio dell'AOS

6) Job Caricamento: Creiamo un job per il refresh delle sequenze numeriche di tutti i moduli:

 static void NumberSeqLoadAll(Args _args)  
 {  
   NumberSeqApplicationModule::loadAll();  
 }  

7) Il Wizard:

  1. Andare in Organization administration > Common > Number sequences > Number sequences.
  2. Click Generate -> Set up number sequences wizard.
  3. Completare il wizard per creare la nuova sequenza per il modulo custom
Se tutto è andato a buon fine se apriamo la lookup sul campo "Codice sequenza numerica" dovremo poter vedere la nuova sequenza con la descrizione equivalente al campo Label del nostro EDT:


Possiamo creare ora un job per iniziare a generare numeri con la sequenza numeri impostata nella tabella dei parametri:

 static void testNSId(Args _args)  
 {;  
   Info(NumberSeq::newGetNum(NSParameters::numRefNSIdNum()).num());  
 }  

8) Creazione form di utilizzo: creiamo un nuovo form in modo tale che ad ogni nuovo record creato venga automaticamente assegnato il numero di sequenza. Il form dovrà avere come datasource una nuova tablella (che chiameremo NSUsage) con almeno un campo di tipo NSid (cioè la stesso EDT creato al punto 1).
Metodi del from:

 public class FormRun extends ObjectRun  
 {  
   NumberSeqFormHandler numberSeqFormHandler;  
 } 
 
 NumberSeqFormHandler numberSeqFormHandler()  
 {  
   if (!numberSeqFormHandler)  
   {  
     numberSeqFormHandler = NumberSeqFormHandler::newForm(  
       NSParameters::numRefNSIdNum().NumberSequenceId,  
        element,  
        NSUsage_ds,  
        fieldnum(NSUsage, NSId));  
   }  
   return numberSeqFormHandler;  
 }  


il datasource dovrà contenere i seguenti 3 metodi:

 public void write()  
 {  
   super();  
   element.numberSeqFormHandler().formMethodDataSourceWrite();  
 } 
 
 public void delete()  
 {  
   element.numberSeqFormHandler().formMethodDataSourceDelete();  
   super();  
 } 
 
 public void create(boolean _append = false)  
 {  
   element.numberSeqFormHandler().formMethodDataSourceCreatePre();  
   super(_append);  
   element.numberSeqFormHandler().formMethodDataSourceCreate();  
 }  

Salviamo il form. A questo punto ogni volta che clicchiamo su nuovo, il campo "Test sequenza numerica" sarà automaticamente popolato con il numero della sequenza numerica selezionata nella tabella dei parametri:



A questo link potete trovare l' XPO del progetto 

venerdì 8 marzo 2013

AX 2012 - Breve introduzione ai report

AX 4.0 - Reset generatore label

In questo breve post vediamo come risolvere un bug presente in AX 4.0 riguardo la generazione delle etichette. In quella versione il generatore degli id delle etichette era gestito da un componente esterno. A volte capita che quando si tenta di creare una nuova etichette il generatore non riesca a recuperare l'ID corretto, assegnando il valore 0 alla nuova etichetta:


Per ovviare a ciò dobbiamo andare in:


strumenti -> strumenti di sviluppo-> controllodi versione->impostazioni->impostazioni di sistema 
cliccare applica e ok

per resettare il componente. Possiamo a questo punto creare correttamente nuove etichette: