static void printCollecionLetterPDF(Args _args)
{
Args args = new Args();
SRSPrintDestinationSettings printJobSettings = new SRSPrintDestinationSettings();
CustCollectionJourController custCollectionJourController;
CustCollectionJourContract custCollectionJourContract;
SrsReportRunImpl srsReportRun;
SrsPrintMgmtExecutionInfo executionInfo = new SrsPrintMgmtExecutionInfo();
FileIOPermission fileIOPermission;
CustCollectionLetterJour custCollectionLetterJour;
Filename fileName = @'C:\temp\custCollectionLetter.pdf';
;
select firstOnly custCollectionLetterJour;
args.record(custCollectionLetterJour);
CustCollectionJourController = new CustCollectionJourController();
CustCollectionJourController.parmReportName(ssrsReportStr(CustCollectionJour, Report));
CustCollectionJourContract = CustCollectionJourController.parmReportContract().parmRdpContract();
CustCollectionJourContract.parmRecordId(custCollectionLetterJour.RecId);
CustCollectionJourController.parmArgs(args);
srsReportRun = CustCollectionJourController.parmReportRun() as SrsReportRunImpl;
CustCollectionJourController.parmReportRun(srsReportRun);
CustCollectionJourController.parmReportContract().parmPrintSettings().printMediumType(SRSPrintMediumType::File);
CustCollectionJourController.parmReportContract().parmPrintSettings().overwriteFile(true);
CustCollectionJourController.parmReportContract().parmPrintSettings().fileFormat(SRSReportFileFormat::PDF);
fileIOPermission = new FileIOPermission(fileName, 'rw');
fileIOPermission.assert();
CustCollectionJourController.parmReportContract().parmPrintSettings().fileName(fileName);
//la riga di codice sottostante salva il report nei file temporanei dell'utente, non ho ben capito a cosa serve
//ma sembra sia obbligatorio altrimenti la stampa solleva un errore
executionInfo.parmOriginalDestinationFileName(WinApi::getTempPath()+conPeek(Global::fileNameSplit(fileName),2)+".pdf");
CustCollectionJourController.parmReportContract().parmReportExecutionInfo(executionInfo);
CustCollectionJourController.runReport();
CodeAccessPermission::revertAssert();
}
martedì 12 novembre 2013
AX 2012 - stampa report su PDF
Questo job stampa il report lettere di sollecito (CustCollectionJour) SSRS su file pdf. Il job è preso da quì apportando qualche piccola modifica.
lunedì 14 ottobre 2013
AX 2012 - Form di lookup che ritornano più valori
Per far sì che un form di lookup restituisca più di un valore al chiamante dobbiamo fare l'override del metodo closeSelect() nel form di lookup.
A questo punto nella form di lookup "LILLookUpTable" facciamo l'override del metodo closeSelect:
Allo scopo creiamo una tabella (e relativa form) principale che chiameremo "LILMainTable" con i campi codice e descrizione. Creiamo poi una tabella (e relariva form) per la lookup che chiameremo "LILLookUpTable" aventi sempre due campi codice e descrizione.
Facciamo l'override del metodo lookup sul campo del datasource "LILMainTable" così:
public void lookup(FormControl _formControl, str _filterStr)
{
Args args = new Args();
FormRun lookUpForm;
;
args.name(formstr(LILLookUpTable));
args.caller(element);
lookUpForm = new FormRun(args);
lookUpForm.init();
this.performFormLookup(lookUpForm, _formControl);
}
public void closeSelect(str _selectString)
{
LILMainTable mainTable;
FormRun formRun;
super(_selectString);
formRun = element.args().caller();
//recupero il buffer in cui sono posizionato al momento della chiamata al form di lookup
mainTable = formRun.dataSource().cursor();
mainTable.Code = LILLookUpTable.Code; //LILLookUpTable è il nome del datasource della form di lookup contenente i valori correnti selezionati
mainTable.Description = LILLookUpTable.Description;
//refresh del datasource di origine per vedere i valori aggiornati
formRun.dataSource().refresh();
}
lunedì 23 settembre 2013
AX 2012 - Jobs
In questo post pubblico due job che ho scritto per un cliente e che possono risultare utili:
Il primo job prende come input il nome di una tabella e salva su file CSV la lista dei campi e le principali prorietà ("Field Name","Label","Help text", "Base type", "EDT") più lunghezza della stringa se il campo è di tipo testo:
Il secondo job analizza un progetto shared e per ogni elemento del progetto salva su file cvs il tipo di elemento, il nome, il livello più basso, quello più alto, l'user che ha creato l'oggetto, la data di creazione, l'eventuale user che ha apportato la modificato e la data di modifica:
Il primo job prende come input il nome di una tabella e salva su file CSV la lista dei campi e le principali prorietà ("Field Name","Label","Help text", "Base type", "EDT") più lunghezza della stringa se il campo è di tipo testo:
static void CRTableInfoExport(Args _args)
{
int i,fieldId;
DictTable DictTable;
DictField DictField;
TableId TableId;
str strFieldName;
Commaio file;
container line,header;
str filename;
#File
;
TableId = tableNum(CustTable); //inserici il nome della tabella
DictTable = new DictTable(TableId);
filename = @'C:\Temp\'+tableId2name(TableId)+'.csv'; //percorso
file = new Commaio(filename , 'W');
file.outFieldDelimiter(';');
if(DictTable)
{
header = ["Field Name","Label","Help text", "Base type", "EDT", "String lenght"];
file.writeExp(header);
for (i=1; i <= DictTable.fieldCnt(); i++)
{
fieldId = DictTable.fieldCnt2Id(i);
DictField = new DictField(TableId, fieldId);
strFieldName = (DictField ? DictField.name() : "");
line = [strFieldName,DictField.label(),DictField.help(),strFmt("%1",DictField.baseType())];
if(extendedTypeId2name(DictField.typeId()))
{
line += extendedTypeId2name(DictField.typeId());
}
else
line += enumId2Name(DictField.enumId());
if(strFmt("%1",DictField.baseType()) == "String")
line += DictField.stringLen();
else
line += '';
file.writeExp(line);
}
}
info("Terminato");
}
Il secondo job analizza un progetto shared e per ogni elemento del progetto salva su file cvs il tipo di elemento, il nome, il livello più basso, quello più alto, l'user che ha creato l'oggetto, la data di creazione, l'eventuale user che ha apportato la modificato e la data di modifica:
static void CRProjectScan(Args _args)
{
ProjName projName;// = "projectName";
ProjectListNode list = infolog.projectRootNode().AOTfindChild("Shared");
TreeNodeIterator ir = list.AOTiterator();
ProjectNode pnProj;
ProjectNode pn;// = list.AOTfindChild(projName);
struct prop;
str strFieldName;
Commaio file;
str filename;
container line,header;
Dialog dialog;
DialogField field;
#properties
utilelements getElementSysInfo (Description nodeType, Description NodeName)
{
utilelementtype uet;
utilelements sue;
;
switch(nodeType)
{
case "BaseEnums":
uet = utilelementtype::Enum;
break;
case "Tables":
uet = utilelementtype::Table;
break;
case "ConfigurationKeys":
uet = utilelementtype::ConfigurationKey;
break;
case "Maps":
uet = utilelementtype::TableMap;
break;
case "ExtendedDataTypes":
uet = utilelementtype::ExtendedType;
break;
case "Views":
uet = utilelementtype::ViewQuery;
break;
case "Queries":
uet = utilelementtype::Query;
break;
case "Classes":
uet = utilelementtype::Class;
break;
case "Forms":
uet = utilelementtype::Form;
break;
case "Reports":
uet = utilelementtype::Report;
break;
case "Jobs":
uet = utilelementtype::Job;
break;
}
select firstonly sue where sue.recordType == uet && sue.name == NodeName;
return sue;
}
void searchAllObj(projectNode rootNode)
{
#TreeNodeSysNodeType
TreeNode childNode;
TreeNodeIterator rootNodeIterator;
utilelements result;
;
if (rootNode)
{
rootNodeIterator = rootNode.AOTiterator();
childNode = rootNodeIterator.next();
while (childnode)
{
if (childNode.AOTgetNodeType() == #NT_PROJECT_GROUP)
searchAllObj(childNode);
else
{
result = getElementSysinfo(rootNode.AOTname(),childNode.AOTname());
line = [rootNode.AOTname(),childNode.AOTname(),enum2str(result.utilLevel),enum2str(childNode.applObjectLayer()), result.createdBy, result.createdDateTime, result.modifiedBy, result.modifiedDateTime];
file.writeExp(line);
}
childNode = rootNodeIterator.next();
}
}
}
;
dialog = new Dialog();
dialog.addText("Select Project:");
field = dialog.addField(typeid(ProjName));
dialog.run();
if (dialog.closedOk())
{
//info(field.value());
projName = field.value();
}
pn = list.AOTfindChild(projName);
if (pn)
{
filename = WinAPI::getSaveFileName(0, ['TEXT FILE', '*' + '.CSV'], @'C:\', 'Save file as','',projName,0);//@'C:\Users\axservice\Desktop\'+tableId2name(TableId)+'.csv'; //percorso
file = new Commaio(filename , 'W');
file.outFieldDelimiter(';');
header = ["Group", "Name", "Lower Level","Highest level", "CreatedBy", "CreatedDateTime", "ModifiedBy", "ModifieddateTime"];
file.writeExp(header);
// info(strFmt("Projet %1:", projName));
pnProj = pn.loadForInspection();
searchAllObj(pnProj);
pnproj.treeNodeRelease();
}
else
error("Projet not found");
}
lunedì 16 settembre 2013
AX 2012 - Global cache
AX 2012 mette a disposizione un potente strumento per memorizzare dei valori e recuperarli quando necessario: la Global chace. Può essere considerato come una speciè di "mappa" in cui posso memorizzare un valore associato ad una chiave e recuperare il valore tramite la chiave:
per inserire un valore nella global cache:
owner e key possono essere, liberi. Tipicamente si usa come owner lo username
es:
per recuperare un valore:
es:
per inserire un valore nella global cache:
globalCache.set(str owner , anytype key , anytype value );
owner e key possono essere, liberi. Tipicamente si usa come owner lo username
es:
SysGlobalCache globalCache = Appl.globalCache();
;
globalCache.set(curUserId(), 4, "Somari su AX");
per recuperare un valore:
value = globalCache.get(str owner , anytype key);
es:
SysGlobalCache globalCache = Appl.globalCache();
str s;
;
s = globalCache.get(curUserId(), 4); //s conterrà la stringa "Somari su AX"
venerdì 2 agosto 2013
AX 2012 - Creazione servizio AIF e consumo tramite C#
In questo articolo vediamo come creare un servizio AIF, esporlo e consumarlo tramite un'applicazione C#, il servizio legge un file CSV e scrive i dati un una tabella. L'articolo è tratto dal white paper Microsoft Dynamics AX 2012 Services.pdf. La tabella di destinazione contterrà due campi, un campo itemid ed un campo itemname
Iniziamo creando la classe datacontract del nostro servizio, che chiameremo CRImportaDataContract che si occuperà di fare il parm del filepath:
A questo punto creimano la classe del servizio che chiameremo CRImportService, settando poi la proprieta RunOn su "Server". La classe contiene il metodo che leggendo il file scrive i dati nella tabella.
Dobbiamo ora deployare il servizio:
Scriviamo ora una piccola applicazione consolo C# che cunsuma il servizio. Aprire visual studio creare una nuova console application. Click col desto su service reference -> add service reference, Nella finestra che si apre inseriamo nel campo address l'indirizzoil valore del campo URI WSDL che troviamo nelle inboud ports di AX e clicchiamo su Go.
Ora il servizio è pronto per essere riahiamato via C#:
[DataContractAttribute('Import')]
public class CRImportDataContract
{
Filename file;
}
[DataMemberAttribute('File')]
public Filename parmFilename(Filename _file = file)
{
;
file = _file;
return file;
}
private static CRImportDataContract construct()
{
return new CRImportDataContract();
}
public static CRImportDataContract newFromFile(Filename _file)
{
CRImportDataContract contract = CRImportDataContract::construct();
;
contract.parmFilename(_file);
return contract;
}
A questo punto creimano la classe del servizio che chiameremo CRImportService, settando poi la proprieta RunOn su "Server". La classe contiene il metodo che leggendo il file scrive i dati nella tabella.
public class CRImportService
{
}
[SysEntryPointAttribute(true)]
public CRImportDataContract getFileName(Filename _file)
{
CRImportDataContract contract;
;
contract = CRImportDataContract::newFromFile(_file);
return contract;
}
[SysEntryPointAttribute(true)]
public boolean insertIntoTable(Filename _file)
{
TextIo inFile;
container line;
Counter records;
//SysOperationProgress simpleProgress;
container fileContainer;
Counter loopCounter;
CRTestTable CRTestTable;
boolean ret;
str filename;
#OCCRetryCount
#AviFiles
filename = _file;
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));
return false;
}
//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++)
{
CRTestTable.clear();
//simpleProgress.incCount();
try
{
line = conPeek(fileContainer, loopCounter); // lettura della riga i-esima
CRTestTable.ItemId = conPeek(line, 1);
CRTestTable.ItemName = conPeek(line, 2);
CRTestTable.insert();
records++;
//simpleprogress.setText(strfmt("@SYS76835", loopCounter, CRTestTable.RecId));
}
catch (Exception::Deadlock)
{
if (xSession::currentRetryCount() < #RetryNum)
{
retry;
}
else
return false;
}
}
ttsCommit;
//info(strFmt("Inseriti %1 record", records));
return true;
}
Dobbiamo ora deployare il servizio:
- Tasto destro sul nodo service group dell' AOT, nuovo service group
- Nelle proprietà immettere il nome del servizio per es ImportService
- Tasto desto sul nodo service group dell'AOT, new service node reference
- Impostare il valore della proprietà Service col valore ImportService
- Tasto desto sul nodo service group a cliccare su Deploy service group
Scriviamo ora una piccola applicazione consolo C# che cunsuma il servizio. Aprire visual studio creare una nuova console application. Click col desto su service reference -> add service reference, Nella finestra che si apre inseriamo nel campo address l'indirizzoil valore del campo URI WSDL che troviamo nelle inboud ports di AX e clicchiamo su Go.
Ora il servizio è pronto per essere riahiamato via C#:
string theFile = "C:\\Users\\AXService\\Desktop\\test2.csv";
bool insert;
ImportServiceClient theService = new ImportServiceClient();
CallContext theContext = new CallContext();
theContext.Company = "CEU";
theContext.Language = "it";
theContext.LogonAsUser = "AXService";
insert = theService.insertIntoTable(theContext, theFile);
Console.WriteLine("Terminato!");
Console.Read();
lunedì 15 aprile 2013
AX 2009 - Duplicazione dati multiriga su form grid
Molte volte capita di dover duplicare i dati presenti su una o più righe di una griglia appartenente ad un form. Generalmente all griglia è associato un datasource specificato nel form.
Per prima cosa possiamo creare un bottone, che una volta schiacciato si assicurerà di effettuare l'operazione di duplica. Per permettere questa operazione in multiriga è necessario impostare la proprietà "MultiSelect" del bottone a "Yes".
Dopodichè basterà inserire questa logica (ne esistono comunque diverse varianti) nel metodo "clicked" del bottone stesso:
Come si vede dal codice basta dichiarare due buffer dello stesso tipo della tabella che è impostata nella griglia, e un oggetto "MultiSelectionHelper" che ci aiuterà a scorrere le righe selezionate.
Il primo buffer (dataSourceLocale) serve per raccogliere i record che mano a mano vogliamo copiare, ovvero ogni singola riga, e il secondo serve per l'inserimento nel database.
Dopo aver inizializzato l'oggetto "MultiSelectionHelper", lo si parametrizza con i dati selezionati dal form e lo si inizia a scorrere partendo dal primo record con "getFirst()" parametrizzando il primo buffer. All'interno del ciclo si parametrizza il secondo buffer con gli stessi valori del primo che viene via via ciclato con "getNext()".
Infine per visualizzare le nuove righe nel form basta effettuare il metodo "executeQuery()" del datasource dello stesso.
Per prima cosa possiamo creare un bottone, che una volta schiacciato si assicurerà di effettuare l'operazione di duplica. Per permettere questa operazione in multiriga è necessario impostare la proprietà "MultiSelect" del bottone a "Yes".
Dopodichè basterà inserire questa logica (ne esistono comunque diverse varianti) nel metodo "clicked" del bottone stesso:
DataSource dataSourceLocale;
DataSource dataSourceNuovo;
MultiSelectionHelper selection = MultiSelectionHelper::construct();
super();
selection.parmDatasource(DataSource_ds);
dataSourceLocale = selection.getFirst();
while (DSLunchOrderslocal.RecId != 0)
{
dataSourceNuovo.field1 = dataSourceLocale.field1;
dataSourceNuovo.field2 = dataSourceLocale.field2;
....
dataSourceNuovo.insert();
dataSourceLocale = selection.getNext();
}
DataSource_ds.executeQuery();
Come si vede dal codice basta dichiarare due buffer dello stesso tipo della tabella che è impostata nella griglia, e un oggetto "MultiSelectionHelper" che ci aiuterà a scorrere le righe selezionate.
Il primo buffer (dataSourceLocale) serve per raccogliere i record che mano a mano vogliamo copiare, ovvero ogni singola riga, e il secondo serve per l'inserimento nel database.
Dopo aver inizializzato l'oggetto "MultiSelectionHelper", lo si parametrizza con i dati selezionati dal form e lo si inizia a scorrere partendo dal primo record con "getFirst()" parametrizzando il primo buffer. All'interno del ciclo si parametrizza il secondo buffer con gli stessi valori del primo che viene via via ciclato con "getNext()".
Infine per visualizzare le nuove righe nel form basta effettuare il metodo "executeQuery()" del datasource dello stesso.
martedì 9 aprile 2013
AX 2009 - Lookup su tabelle e campi
In questo post vediamo come creare un un semplice form con due campi: il primo campo apre una lookup su tutte le tabelle, il secondo apre una lookup su tutti i campi della tabella selezionata. Il nostro form dovrà contenere due controlli di ti po string che chiameremo rispettivamente "Tables" in autodeclaration e "Fields". Effettuiamo l'override del metodo lookup sul campo tables:
A livello di class declaration dichiariamo una variabile che conterrà l'id della tabella selezionata:
Questa variabile verrà settata nel modified field del controllo "Tables"
Scriviamo a questo punto il metodo lookup del controllo "Fields"
Possiamo verificare il funzionamento del form selezionando "CustTable"
static void lookup(FormStringControl _ctrl)
{
SysTableLookup sysTableLookup =
SysTableLookup::newParameters(tablenum(UtilidElements), _ctrl);
Query query = new Query();
QueryBuildDataSource queryBuildDataSource;
QueryBuildRange nameQBR, typeQBR;
;
sysTableLookup.addLookupfield(fieldnum(UtilidElements, Name));
sysTableLookup.addLookupfield(fieldnum(UtilidElements, Id));
queryBuildDataSource = query.addDataSource(tablenum(UtilidElements));
typeQBR = queryBuildDataSource.addRange(fieldnum(UtilidElements, recordType));
typeQBR.value(SysQuery::value(UtilElementType::Table));
sysTableLookup.parmQuery(query);
sysTableLookup.performFormLookup();
}
A livello di class declaration dichiariamo una variabile che conterrà l'id della tabella selezionata:
public class FormRun extends ObjectRun
{
TableId TableId;
}
Questa variabile verrà settata nel modified field del controllo "Tables"
public boolean modified()
{
boolean ret;
ret = super();
TableId = TableName2Id(this.text());
return ret;
}
Scriviamo a questo punto il metodo lookup del controllo "Fields"
public void lookup()
{
SysTableLookup sysTableLookup =
SysTableLookup::newParameters(tablenum(UtilidElements), this);
Query query = new Query();
QueryBuildDataSource queryBuildDataSource;
QueryBuildRange nameQBR, typeQBR;
;
sysTableLookup.addLookupfield(fieldnum(UtilidElements, Name));
sysTableLookup.addLookupfield(fieldnum(UtilidElements, Id));
queryBuildDataSource = query.addDataSource(tablenum(UtilidElements));
nameQBR = queryBuildDataSource.addRange(fieldnum(UtilidElements,
ParentId));
nameQBR.value(queryValue(TableId));
typeQBR = queryBuildDataSource.addRange(fieldnum(UtilidElements, recordType));
typeQBR.value(SysQuery::value(UtilElementType::TableField));
sysTableLookup.parmQuery(query);
sysTableLookup.performFormLookup();
}
Possiamo verificare il funzionamento del form selezionando "CustTable"
Iscriviti a:
Post (Atom)



