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
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
Per cambiare magazzino dobbiamo:
- Prendere il codice attuale della dimensione inventariale
- Modificare il magazzino
- Ricalcolare il nuovo codice (o cercarlo se già esiste)
- 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.
Nel primo si specificano le caratteristiche della sequenza, il secondo deve restituire l'enumerato NumberSeqModule creato in precedenza.
Inoltre,bisogna modificare i metodi construct() e moduleList() nella classe NumberSeqReference stessa per aggiungere i riferimenti alla nuova classe-modulo creata.
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():
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ì:
È 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:
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.
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!
lunedì 24 settembre 2012
AX 2009 - Aggiunta e gestione di immagini come campo di tabella
L'argomento che andiamo a trattare è il caricamento e la visualizzazione di un immagine.
Un caso frequente di utilizzo sono, ad esempio, dei listini articoli con l'immagine dell'articolo dentro AX.
Per prima cosa dovremo creare sulla nostra tabella un nuovo campo di tipo Container,
e impostare le proprietà del campo come segue:
le modifiche a livello di tabella finisco qui, vediamo ora come fare per visualizzare e modificare l'immagine da un form.
Sul form avremo bisogno di due controlli, un campo di tipo Windows e Button.
impostiamo le proprietà dei controlli come segue:
Adesso ci mancano solo i metodi per gestire il tutto.
Iniziamo con l'override del metodo clicked del bottone appena creato.
void clicked()
{
FilenameFilter filter = ['Image Files','*.bmp;*.jpg;*.gif;*.jpeg'];
BinData binData = new BinData();
str extention, path, nameOfFile;
super();
imageFilePathName = WinAPI::getOpenFileName(element.hWnd(),filter,'', "@SYS53008", '','');
if (imageFilePathname && WinAPI::fileExists(imageFilePathName))
{
[path, nameOfFile, extention] = fileNameSplit(imageFilePathName);
if (extention == '.bmp' ||
extention == '.jpg' ||
extention == '.gif' ||
extention == '.jpeg')
{
binData.loadFile(imageFilePathName);
imageContainer = binData.getData();
element.showImage();
element.insert();
}
else
{
throw error("@SYS89176");
}
}
}
In seguito dobbimo realizzare i due metodi 'showImage' e 'insert' che abbiamo appena utilizzato. Questi andranno creati a livello dei metodi globali del form. Il metodo 'showImage' serve a visualizzare l'immagine appena caricata mentre il metodo 'insert' ad inserirla e salvarla sul database.
void showImage()
{
Image logoImage;
;
try
{
element.lock();
if (imageContainer)
{
logoImage = new Image();
logoImage.setData(imageContainer);
Image.image(logoImage);
}
else
{
Image.imageResource(0);
Image.widthValue(32);
Image.heightValue(32);
}
element.resetSize();
element.unLock();
}
catch (Exception::Warning)
{
throw error(strfmt("@SYS19312", imageFilePathName));
}
}
void insert() { ; ttsbegin; line = MyTable::find(MyTable_
ds.
KeyField, true); if(line) { line.Image = imageContainer; line.update(); } ttscommit; }
E' buona norma non mettere l'immagine nella griglia della panoramica principale ma inserirla in uno degli altri tab del nostro form in modo da non appesantire lo scorrimento del dataSource, dovremo allora aver cura anche di effettuare l'override del metodo 'pageActivated', del tab dove inseriremo l'immagine, come segue:
public void pageActivated()
{
;
super();
element.loadImage();
}
void loadImage() { Image img; MyTable table; ;
table
= MyTable::find(MyTable_ds.K
eyField)
; if (table.Image) { img = new Image(); img.setData(table.Image); Image.image(img); } else { Image.image(null); } }
Il risultato che otterremo sarà qualcosa di simile al seguente... :)
giovedì 20 settembre 2012
AX 2012 - D365FFO - Framework propagazione del valore di testata ordine cliente a tutte le righe
La propagazione di un dato presente in testa ordine cliente a tutte le righe è uno scenario molto frequente in AX. Per questo motivo esite un framework standard che AX mette a disposizione per effettuare questa operazione.
In questo articolo vedremo come utilizzare questo framework estendendo il funzionamento ad un nuovo campo (myField di tipo stringa) che aggiungeremo sia in testata ordine che in riga. L'articolo si riferisce alla versione AX 2012 ma il funzionamento è rimasto invariato sia in AX 4.0 che in AX 2009.
Per prima cosa andiamo in: Contabilità clienti -> impostazioni -> parametri contabilità clienti -> aggiornamenti e clicchiamo sul pulsante "Aggiorna righe ordine":
In questo form sono presenti i campi di testata che attualmente sono gestiti da tale framework. Per ogni campo sono presenti tre possibili azioni:
Mai: Il valore non viene mai propagato alle righe
Sempre: Il valore viene sempre propagato alle righe
Avviso: Il valore viene propagato se l'utente autorizza l'azione
1)Per prima cosa occorre aggiungere il campo "MyField" a SalesTable ed a SalesLine.
2) Aggiungete il campo SalesTable.MyField al FieldGroup HeaderToLineUpdate
3)Modifichiamo la classe aXSalesTable aggiungendo nella class declaration la varianbile che conterrà il valore del campo e successivamente creiamo il metodo parm per tale variabile:
4)Modifichiamo poi la classe aXSalesLine:
5)aggiungere nel metodo setTableFields() il nostro metodo in coda agli altri
6)Modifichiamo infine il metodo lineUpdateDescription() della classe SalesTable2LineFiled aggiungendo allo switch il case che gestisce il nostro campo:
Riaprendo il form dei parametri di aggiornamento dovremmo ora vedere il nuovo campo:
A questo punto occorre solo rigenerare il contenuto della tabella SalesTable2LineParameters per far sì che AX posso gestire l'aggiornamento . Dobbiamo quindi per prima cosa cancellare tutto il contenuto per poi rigenerarlo tramite il metodo initiate(). Questa operazione và fatta per ogni company. Possiamo scrivere il seguente job che esegue tutte le operazioni:
D365FFO:
Per effettuare la stessa modifica su D365 i passaggi sono quasi analoghi, ovviamente i passi 3) 4) e 5) vanno implementati tramite CoC, mentre per il punto 6) occorre sottoscrivere il delegato "lineUpdateDescriptionDelegate" così:
In questo articolo vedremo come utilizzare questo framework estendendo il funzionamento ad un nuovo campo (myField di tipo stringa) che aggiungeremo sia in testata ordine che in riga. L'articolo si riferisce alla versione AX 2012 ma il funzionamento è rimasto invariato sia in AX 4.0 che in AX 2009.
Per prima cosa andiamo in: Contabilità clienti -> impostazioni -> parametri contabilità clienti -> aggiornamenti e clicchiamo sul pulsante "Aggiorna righe ordine":
In questo form sono presenti i campi di testata che attualmente sono gestiti da tale framework. Per ogni campo sono presenti tre possibili azioni:
Mai: Il valore non viene mai propagato alle righe
Sempre: Il valore viene sempre propagato alle righe
Avviso: Il valore viene propagato se l'utente autorizza l'azione
1)Per prima cosa occorre aggiungere il campo "MyField" a SalesTable ed a SalesLine.
2) Aggiungete il campo SalesTable.MyField al FieldGroup HeaderToLineUpdate
3)Modifichiamo la classe aXSalesTable aggiungendo nella class declaration la varianbile che conterrà il valore del campo e successivamente creiamo il metodo parm per tale variabile:
public str parmMyField(str_MyField= '')
{
if (!prmisDefault(_MyField))
{
this.setField(fieldNum(SalesTable, MyField), _MyField);
}
return SalesTable.MyField;
}
4)Modifichiamo poi la classe aXSalesLine:
public str parmMyField(str_MyField= '')
{
if (!prmisDefault(_MyField))
{
this.setField(fieldNum(SalesLine, MyField), _MyField);
}
return SalesLine.MyField;
}
protected void setMyField()
{
if (this.isMethodExecuted(funcName(), fieldNum(SalesLine, MyField)))
{
return;
}
this.setAxSalesTableFields();
if (this.isAxSalesTableFieldsSet() || this.AxSalesTable().isFieldModified(fieldNum(SalesTable,MyField)))
{
this.parmMyField(this.axSalesTable().parmMyField());
}
}
5)aggiungere nel metodo setTableFields() il nostro metodo in coda agli altri
this.setMyField();
6)Modifichiamo infine il metodo lineUpdateDescription() della classe SalesTable2LineFiled aggiungendo allo switch il case che gestisce il nostro campo:
case fieldnum(SalesTable, MyField):
return fieldid2pname(tableNum(SalesLine), fieldNum(SalesLine, MyField));
Riaprendo il form dei parametri di aggiornamento dovremmo ora vedere il nuovo campo:
A questo punto occorre solo rigenerare il contenuto della tabella SalesTable2LineParameters per far sì che AX posso gestire l'aggiornamento . Dobbiamo quindi per prima cosa cancellare tutto il contenuto per poi rigenerarlo tramite il metodo initiate(). Questa operazione và fatta per ogni company. Possiamo scrivere il seguente job che esegue tutte le operazioni:
static void SalesTable2LineParametersInit(Args _args)
{
DataArea DataArea;
SalesTable2LineParameters SalesTable2LineParameters;
;
ttsBegin;
while select DataArea where dataArea.isVirtual == NoYes::No
{
changeCompany(DataArea.id)
{
delete_from SalesTable2LineParameters;
SalesTable2LineParameters::initiate();
}
}
ttsCommit;
info("terminato");
}
D365FFO:
Per effettuare la stessa modifica su D365 i passaggi sono quasi analoghi, ovviamente i passi 3) 4) e 5) vanno implementati tramite CoC, mentre per il punto 6) occorre sottoscrivere il delegato "lineUpdateDescriptionDelegate" così:
[SubscribesTo(classStr(SalesTable2LineField), delegateStr(SalesTable2LineField, lineUpdateDescriptionDelegate))]
public static void SalesTable2LineField_lineUpdateDescriptionDelegate(FieldId _fieldId, TableId _tableId, EventHandlerResult _result)
{
FieldLabel description;
switch(_fieldId)
{
case fieldNum(SalesTable, MyField) :
description = fieldId2pname(tableNum(SalesLine), fieldNum(SalesLine, MyField));
_result.result(description);
break;
}
}
martedì 18 settembre 2012
Accedere alle proprietà di un datasource via codice
Molto spesso è utile poter accedere alle proprietà di un datasource via X++. Supponiamo di voler modificare run-time la proprietà "Allow edit" del campo "ItemName":
inventTable_DS.object(fieldNum(InventTable,ItemName)).allowEdit(condition);
lunedì 3 settembre 2012
AX 2009 - Aggiungere un campo array come range di un QueryBuildDataSource
Una delle cose più semplici su Ax è definire un range ad un determinato QueryBuildDataSource da voi definito, in modo da filtrare la vostra query. Infatti:
Non del tutto banale è definire un campo array come range (come ad esempio il campo Dimension). Ecco la semplice ma non troppo conosciuta soluzione:
Query query = new Query();
QueryBuildDataSource queryBuildDataSource;
str value;
;
queryBuildDataSource = query.addDataSource(tablenum(CustTrans));
queryBuildDataSource.addRange(fieldnum(CustTrans, TransDate)).value(queryValue(today()));
Non del tutto banale è definire un campo array come range (come ad esempio il campo Dimension). Ecco la semplice ma non troppo conosciuta soluzione:
queryBuildDataSource.addRange(FieldId2Ext(FieldNum(CustTrans,Dimension),1)).value(queryValue(value));
AX 2009 - Lookup custom su dialog
In questo articolo vedremo come effettuare l'override del metodo lookup su un campo di una dialog. Attenzione: non è possibile effettuare l'override del metodo lookup se si stà lanciando un report
Supponiamo di voler effettuare la lookup custum sul campo itemId di una dialog e di vol filtrare solo gli articoli di tipo formula.
La nostra classe, oltre i metodi ( dialog(), getFromDialog(), pack() e unpack()) dovà contenere l'override del metodo dialogPostRun():
a questo punto facciamo tasto destro sul controllo per vedere il nome che AX gli ha assegnato:
Come vediamo AX ha assegnato al controllo il nome "Fld1_1". Di conseguenza per effettuare l'override della lookup bisogna aggiungere il metodo Fld1_1_lookup(), se avessimo voluto fare l'override del modified avremmo dovuto aggiungere un metodo chiamato Fld1_1_modifie, in generale quindi la sintassi è <nome controllo>_nome_metodo():
A questo punto la nostra lookup sugli articoli mostrerà solo quelli di tipo "Formula":
La stessa cosa potrebbe essere fatta in teoria anche su un campo del "Seleziona",anche se è sconsigliabile farlo, qualora la lookup standard non fosse disponibile:
La nostra classe dovrà così essere modificata:
conviene infine disattivare il pulsante "Select" ovverridando il metodo showQuerySelectButton() per evitare che l'utente, cambiando range sulla query, possa sovrascrivere il nostro controllo "Fld1_1"
Supponiamo di voler effettuare la lookup custum sul campo itemId di una dialog e di vol filtrare solo gli articoli di tipo formula.
La nostra classe, oltre i metodi ( dialog(), getFromDialog(), pack() e unpack()) dovà contenere l'override del metodo dialogPostRun():
public void dialogPostRun(DialogRunbase _dialog)
{
;
_dialog.dialogForm().formRun().controlMethodOverload(true);
_dialog.dialogForm().formRun().controlMethodOverloadObject(this);
_dialog.formRun().controlMethodOverload(true);
_dialog.formRun().controlMethodOverloadObject(this);
super(_dialog);
}
a questo punto facciamo tasto destro sul controllo per vedere il nome che AX gli ha assegnato:
Come vediamo AX ha assegnato al controllo il nome "Fld1_1". Di conseguenza per effettuare l'override della lookup bisogna aggiungere il metodo Fld1_1_lookup(), se avessimo voluto fare l'override del modified avremmo dovuto aggiungere un metodo chiamato Fld1_1_modifie, in generale quindi la sintassi è <nome controllo>_nome_metodo():
boolean Fld1_1_lookup()
{
FormStringControl control = dialog.formRun().controlCallingMethod();
SysTableLookup sysTableLookup = SysTableLookup::newParameters(tablenum(InventTable), control);
Query query = new Query();
QueryBuildDataSource queryBuildDataSource;
QueryBuildRange queryBuildRange;
;
//lista dei campi da mostrare nella lookup
sysTableLookup.addLookupfield(fieldnum(InventTable, ItemId));
sysTableLookup.addLookupfield(fieldnum(InventTable, ItemName));
sysTableLookup.addLookupfield(fieldnum(InventTable, ItemType));
//filtri
query.addDataSource (tableNum( InventTable) ).addRange( fieldNum( InventTable,ItemType)). value(queryValue(ItemType::Formula));
sysTableLookup.parmQuery(query);
sysTableLookup.performFormLookup();
return true;
}
A questo punto la nostra lookup sugli articoli mostrerà solo quelli di tipo "Formula":
La stessa cosa potrebbe essere fatta in teoria anche su un campo del "Seleziona",anche se è sconsigliabile farlo, qualora la lookup standard non fosse disponibile:
La nostra classe dovrà così essere modificata:
class LIL_TestQueryLookup extends RunBaseBatch
{
Range range;
SysQueryRun queryrun;
DialogRunbase dialog;
#define.CurrentVersion(1)
#define.Version1(1)
#localmacro.CurrentList
range
#endmacro
}
public Object dialog()
{
FormBuildStringControl queryField;
;
dialog = super(dialog);
//settiamo le proprietà del controllo
//per renderlo attivo, editabile e far apparire il pulsante di lookup
queryField = Dialog.dialogForm().control("Fld1_1");
queryField.enabled(true);
queryField.allowEdit(true);
queryField.lookupButton(FormLookupButton::Always);
queryField.text("");
return dialog;
}
public boolean getFromDialog()
{
FormBuildStringControl queryField;
boolean ret;
ret = super();
queryField = Dialog.dialogForm().control("Fld1_1");
range = queryField.text();
return ret;
}
conviene infine disattivare il pulsante "Select" ovverridando il metodo showQuerySelectButton() per evitare che l'utente, cambiando range sulla query, possa sovrascrivere il nostro controllo "Fld1_1"
boolean showQuerySelectButton()
{
return false;
}
venerdì 31 agosto 2012
AX 2009 - Logica testata righe: impostazioni e zero codice
La logica testata-righe in un form è una procedura piuttosto comune e richiesta.
Essa è rappresentabile in più modi (ad esempio modificando il metodo "active" del datasource di testata),
ma la procedura più semplice e veloce, se non avete particolari pretese, si esegue tramite due semplici impostazioni.
Il primo step di questa semplice soluzione consiste nell'impostare una normale relazione nella tabella di linea che colleghi logicamente il campo in comune fra le due tabelle:
Ora, dopo aver aggiunto i due datasource al Form che ci interessa , si imposta la proprietà JoinSource del datasource di linea con quello di testata come da immagine:
Ora vi basterà aggiungere due grid nel design del form, una avente i campi della tabella di testata e una avente i campi della tabella di linea.
Quando selezionerete una riga dalla grid di testata saranno mostrate automaticamente nella grid di linea le righe ad essa associate!
Essa è rappresentabile in più modi (ad esempio modificando il metodo "active" del datasource di testata),
ma la procedura più semplice e veloce, se non avete particolari pretese, si esegue tramite due semplici impostazioni.
Il primo step di questa semplice soluzione consiste nell'impostare una normale relazione nella tabella di linea che colleghi logicamente il campo in comune fra le due tabelle:
Ora, dopo aver aggiunto i due datasource al Form che ci interessa , si imposta la proprietà JoinSource del datasource di linea con quello di testata come da immagine:
Quando selezionerete una riga dalla grid di testata saranno mostrate automaticamente nella grid di linea le righe ad essa associate!
AX 2009 - Importazione da XML
Il codice sottostante importa i dati in SalesTable, SalesLine dal file XML generato tramite esportazione:
public void run()
{
SalesTable SalesTable;
SalesLine SalesLine;
int i,numSales;
SalesStatus SalesStatus;
InventTransId InventTransId;
SalesId salesid;
SysOperationProgress prog = new SysOperationProgress();
XMLNodeList ReqNodeList;
XMLNodeList LinesNodeList;
SysOperationProgress simpleProgressTask;
XMLDocument doc;
XMLNode rootNode;
XMLNodeList salesLines;
XMLElement element,lineElement;
;
doc = new XMLDocument();
doc.load(fileName);
rootNode = doc.documentElement();
ReqNodeList = rootNode.selectNodes("/Sales/SalesTable"); // caricamento della lista degli ordini
numSales = ReqNodeList.length();
simpleProgressTask = SysOperationProgress::newGeneral(#aviUpdate, 'Sales import...',numSales);
ttsbegin;
prog.setCaption("Import in AX in progress...");
prog.setAnimation(#AviTransfer);
i = 0;
for(element = ReqNodeList.nextNode(); element; element = ReqNodeList.nextNode()) // ciclo sugli ordini cliente
{
simpleProgressTask.incCount(1);
simpleProgressTask.setText(strfmt("Percent: %1"+'%', (i/numSales)*100));
simpleProgressTask.update(true);
salesId = element.selectSingleNode("@SalesId").value();
if(SalesTable::find(salesId)) //l'ordine è gà prensente, occorre fare un update
{
select forupdate SalesTable where SalesTable.SalesId == salesId;
SalesTable.SalesStatus = str2enum(SalesStatus, element.selectSingleNode(fieldstr(SalesTable, SalesStatus)).innerText());
//Inserire quì gli altri campi di testata da importare
SalesTable.Update();
}
else // ordine non presente, occorre crearlo e fare l'insert
{
SalesTable.SalesId = salesId;
SalesTable.SalesStatus = str2enum(SalesStatus, element.selectSingleNode(fieldstr(SalesTable, SalesStatus)).innerText());
//Inserire quì gli altri campi di testata da importare
SalesTable.Insert();
}
LinesNodeList = element.selectNodes("Lines/SalesLine"); // caricamento della lista delle righe dell'ordine che stò ciclando
for(lineElement = LinesNodeList.nextNode(); lineElement; lineElement = LinesNodeList.nextNode()) // ciclo sulle righe
{
InventTransId = lineElement.selectSingleNode("@InventTransId").value();
if(SalesLine::findInventTransId(InventTransId)) // la riga ordine è gà prensente, occorre fare un update
{
select forupdate SalesLine where SalesLine.InventTransId == InventTransId;
SalesLine.LineNum = str2num(lineElement.selectSingleNode(fieldstr(SalesLine, LineNum)).innerText());
//Inserire quì gli altri campi di riga da importare
SalesLine.Update();
}
else // la riga d'ordine non è presente, occorre crearlo e fare l'insert
{
SalesLine.InventTransId = InventTransId;
SalesLine.LineNum = str2num(lineElement.selectSingleNode(fieldstr(SalesLine, LineNum)).innerText());
//Inserire quì gli altri campi di riga da importare
SalesLine.Insert();
}
}
i++;
}
ttscommit;
}
giovedì 30 agosto 2012
AX 2009 - Esportazione dati in XML
Come primo articolo vedremo come esportare in XML dati di una tabella e successivamente effettuare l'import in AX.
Come esempio esporteremo dati dalle tabelle SalesTable e SalesLine collegate tra di loro secondo la logica testata-righe, tale soluzione si può comunque estendere a qualsiasi tabella.
Il campo che lega le due tabelle è SalesId.
L'XML che andremo a creare rispecchierà tale logica nella sua struttura.
In particolare esporteremo due campi di testata (SalesId, SalesStatus) e due campi di riga(InventTransId, LineNum)
L'avanzamento complessivo della procedura è dato dalla progressbar.
Questo il file XML che andremo ad ottenere:
Come esempio esporteremo dati dalle tabelle SalesTable e SalesLine collegate tra di loro secondo la logica testata-righe, tale soluzione si può comunque estendere a qualsiasi tabella.
Il campo che lega le due tabelle è SalesId.
L'XML che andremo a creare rispecchierà tale logica nella sua struttura.
In particolare esporteremo due campi di testata (SalesId, SalesStatus) e due campi di riga(InventTransId, LineNum)
L'avanzamento complessivo della procedura è dato dalla progressbar.
public void export()
{
SalesTable SalesTable, ST;
SalesLine SalesLine;
#AviFiles
SysOperationProgress simpleProgressTask, simpleProgressReq;
int numSales;
int row;
XmlDocument doc;
XmlElement nodeTable;
XmlElement nodeXml;
XmlElement nodeStatus;
XmlElement linesRoot;
XmlElement nodeSalesLine;
XmlElement nodeLineNum;
;
doc = XmlDocument::newBlank(); // creo un XML vuoto
nodeXml = doc.createElement('Sales'); // imposta il nodo radice
doc.appendChild(nodeXml); // aggiungi il nodo radice al documento
row = 0;
select count(recId) from ST;
numSales = ST.RecId;
simpleProgressTask = SysOperationProgress::newGeneral(#aviUpdate, 'Sales export...', numSales);
new InteropPermission(InteropKind::ClrInterop).assert();
while select SalesTable
{
simpleProgressTask.incCount(1);
simpleProgressTask.setText(strfmt("Percent: %1"+'%', (row/numSales)*100));
simpleProgressTask.update(true);
nodeTable = doc.createElement(tablestr(SalesTable));
nodeTable.setAttribute(fieldstr(SalesTable, SalesId), SalesTable.SalesId); // imposto il valore dell'attributo dell'elemento
nodeXml.appendChild(nodeTable);
nodeStatus = doc.createElement(fieldstr(SalesTable, SalesStatus));
nodeStatus.appendChild(doc.createTextNode(enum2str(SalesTable.SalesStatus))); // il nome dell'elemento coincide con il nome del campo
nodeTable.appendChild(nodeStatus);
// aggiungi quì altri campi di testata da esportare
linesRoot = doc.createElement('Lines'); //elemento radice che conterrà le righe
nodeTable.appendChild(linesRoot);
while select SalesLine
where SalesLine.SalesId == SalesTable.SalesId
{
nodeSalesLine = doc.createElement(tablestr(SalesLine));
nodeSalesLine.setAttribute(fieldstr(SalesLine, InventTransId), SalesLine.InventTransId);
linesRoot.appendChild(nodeSalesLine);
nodeLineNum = doc.createElement(fieldstr(SalesLine, lineNum));
nodeLineNum.appendChild(doc.createTextNode(num2str(SalesLine.LineNum,-1,-1,-1,-1)));
nodeSalesLine.appendChild(nodeLineNum);
// aggiungi quì gli altri campi di riga da esportare
}
row++;
}
doc.save(fileName);
CodeAccessPermission::revertAssert();
return;
}
Questo il file XML che andremo ad ottenere:
<?xml version="1.0" encoding="utf-8"?>
<Sales>
<SalesTable SalesId="C0901542">
<SalesStatus>Ordine aperto</SalesStatus>
<Lines />
</SalesTable>
<SalesTable SalesId="C0901724">
<SalesStatus>Ordine aperto</SalesStatus>
<Lines>
<SalesLine InventTransId="926038">
<lineNum>32,00</lineNum>
</SalesLine>
<SalesLine InventTransId="926038">
<lineNum>33,00</lineNum>
</SalesLine>
<SalesLine InventTransId="926038">
<lineNum>34,00</lineNum>
</SalesLine>
<SalesLine InventTransId="925879.1">
<lineNum>110,00</lineNum>
</SalesLine>
</Lines>
</SalesTable>
</Sales>
Benvenuti!
Ciao a tutti!
Questo blog nasce con l'intento di fornire soluzioni e approcci alle problematiche più comuni (e non) agli sviluppatori (o presunti tali) di Microsoft Dynamics AX (per chi non lo conoscesse http://www.microsoft.com/en-us/dynamics/erp-ax-overview.aspx).
Ovviamente sarà nostra premura pubblicare tali soluzioni nella maniera più corretta possibile, ma è anche nostro obiettivo coinvolgere più persone possibili, specialmente in italia dove non esistono particolari punti di riferimento.
A presto con i primi articoli....
Questo blog nasce con l'intento di fornire soluzioni e approcci alle problematiche più comuni (e non) agli sviluppatori (o presunti tali) di Microsoft Dynamics AX (per chi non lo conoscesse http://www.microsoft.com/en-us/dynamics/erp-ax-overview.aspx).
Ovviamente sarà nostra premura pubblicare tali soluzioni nella maniera più corretta possibile, ma è anche nostro obiettivo coinvolgere più persone possibili, specialmente in italia dove non esistono particolari punti di riferimento.
A presto con i primi articoli....
Iscriviti a:
Post (Atom)