Páginas

Como converter um campo Date para UtcDateTime

Para converter o campo Date para um utcDateTime podemos utilizar o método newDateTime() da classe DateTimeUtil da seguinte maneira:
  1. static void Job49(Args _args)
  2. {
  3.     UtcDateTime dt;
  4.     Date d = today();
  5.     ;
  6.     dt = DateTimeUtil::newDateTime(d, str2time('00:00:00'));
  7.     info(strFmt('%1', dt));
  8. }
[]'s
Alexssander

Utilizando o .Net Business Connector

Olá,

No início de Novembro precisei entender como utilizar a integração do AX via .NET Business Connector e como até hoje não havia participado de um projeto que utilizava dessa tecnologia não entendia na pratica como funcionava.

Pesquisando na net encontrei um post muito bom feito para o AX 4.0 que foi feito pelo Edvandro Santos que diga-se de passagem tem um blog muito bom, com bastante informações e dicas.


No AX 2009 é utilizado da mesma maneira a diferença é que a .dll do Business Connector nos arquivos de instalação do AX encontra-se em um outro caminho ("C:\Program Files\Microsoft Dynamics AX\50\BusinessConnector\Bin\Microsoft.Dynamics.BusinessConnectorNet.dll").

Na integração que criei o sistema legado é desenvolvido em C# então desenvolvi um código no evento do botão salvar do sistema para enviar as informações geradas para dentro do Dynamics AX.

[]'s
Alexssander

Exibir valor da label de um extended data type

Olá, recebi do meu amigo Francisco Oliveira uma dica bem interessante sobre como exibir o valor da label do um extended data type:
  1. static void Job124(Args _args)
  2. {
  3. ;
  4.  
  5.     info(new SysDictType(extendedtypenum(AccountNum)).label());
  6. }
[]'s
Alexssander

Como iniciar um Form já maximizado utilizando a classe WinAPI

Outro dia estava realizando uma custom em que era necessário o form abrir maximizado, pois utilizava imagens do tipo bitmap e ao iniciar o form as imagens não apareciam por inteiro.

Conversando com um grande amigo Leandro Honório, tentamos uma solução e não encontramos naquele momento, porém hoje ele me disse que havia descoberto como realizar essa questão utilizando a classe WinAPI que é uma interface para APIs do Windows.
Vamos sobreescrever o método run() do formulário que desejamos maximizar após o super(), com o seguinte código:
  1. WinAPI::maximizeWindow(element.hWnd();
Caso necessite podemos utilizar também o método forceMaximizeWindow() da mesma classe.

Nosso método ficou da seguinte forma:
  1. public void run()
  2. {
  3.     super();
  4.  
  5.     WinAPI::maximizeWindow(element.hWnd());
  6. }
Espero que possa ajudar.

[]’s
Alexssander

Utilizar uma Sequência Numérica em um Report ou Web

Hoje pela manha um grande amigo Renato Honório, me passou uma grande dica para complementar o post anterior sobre Sequência Numérica.

Um método simples que recebe o próximo número da sequência criada no post anterior, para ser utilizado diretamente em um report ou web.

O método ficou da seguinte maneira:
  1. static void usingWithNoForm(Args _args)
  2. {
  3.     str aux;
  4.     ;
  5.  
  6.     aux = NumberSeq::newGetNum(NumberSequenceReference::find(typeId2ExtendedTypeId(typeid(MyNumberSequence))),true).num();
  7.     info(strfmt("Seq: %1", aux));
  8. }
Mais uma vez agradecer ao Renato Honório e conceder os créditos desse post para ele.

[]’s
Alexssander

Como criar uma nova sequência numérica customizada

Outro dia meu amigo Abraão estava com dúvidas para criar uma nova sequência numérica, por isso estou postando um passo a passo para a criação.

1- Primeiro criei um ExtendedDataType MyNumberSequence.

2- Depois criei uma tabela chamada MyTable, para servir de exemplo e adicionei o ExtendedDataType que será nosso campo sequencial.

3- No nosso caso vamos criar uma sequência numérica para o módulo de Contas à Receber, então na classe NumberSeqReference_Customer vamos alterar o método loadModule() e adicionar o seguinte trecho de código:
  1. if (isConfigurationkeyEnabled(configurationkeynum(MyConfigKey))
  2. {
  3.     numRef.DataTypeId = typeId2ExtendedTypeId(typeid(MyNumberSequence)); //Nome do EDT
  4.     numRef.ConfigurationKeyId = configurationkeynum(MyConfigKey); //Nome da ConfigKey
  5.     numRef.ReferenceLabel = literalstr("Nome q aparece na tela de parâmetros para atribuir a sequencia");
  6.     numRef.ReferenceHelp = literalstr("Nova Sequência");
  7.     numRef.WizardContinuous = true;
  8.     numRef.WizardManual = NoYes::No;
  9.     numRef.WizardAllowChangeDown = NoYes::No;
  10.     numRef.WizardAllowChangeUp = NoYes::No;
  11.     numRef.SortField = 20; //Muito importante, respeitar a sequencia do método
  12.     numRef.WizardHighest = 999999;
  13.     this.create(numRef);
  14. }
Obs.: Como já adicionei em comentário no método é muito importante respeitar a senquencia do método, e para esse caso criei também uma ConfigurantionKey para controle de segurança.

4- Agora vamos criar um método na SalesParameters (pois estamos criando a sequência no módulo de Contas à Receber)
  1. static client server NumberSequenceReference numRefMyNumberSequence()
  2. {
  3.     ;
  4.     return NumberSeqReference::findReference(typeId2ExtendedTypeId(typeid(MyNumberSequence))); //Nome do seu EDT
  5. }
5- Agora vamos criar a nossa sequência numérica no seguinte caminho:

Básico - Configurações - Sequências Numéricas - Sequências Numéricas

6- Agora vamos atribuir a sequência numérica criada ao nosso formulário no caminho:

Contas à Receber - Configurações - Parâmetros, na aba Sequências Numéricas

7- Agora vamos criar um formulário customizado utilizando nossa tabela, declarar uma variável global:
  1. public class FormRun extends ObjectRun
  2. {
  3.     NumberSeqFormHandler numberSeqFormHandler;
  4. }
E criar o seguinte método no formulário:
  1. NumberSeqFormHandler numberSeqFormHandler()
  2. {
  3.     if (!numberSeqFormHandler)
  4.     {
  5.         numberSeqFormHandler = numberSeqFormHandler::newForm(SalesParameters::numRefMyNumberSequence().NumberSequence, element, MyTable_DS, fieldNum(MyTable, MyNumberSequence));
  6.     }
  7.     return numberSeqFormHandler;
  8. }
8- Agora vamos sobreescrever os métodos create(), write() e delete() do DataSource do formulário, com os seguintes códigos:
  1. public void create(boolean _append = false, boolean extern = false)
  2. {
  3.     ;
  4.     element.numberSeqFormHandler().formMethodDataSourceCreatePre();
  5.     super(_append);
  6.     if(!extern)
  7.     {
  8.         element.numberSeqFormHandler().formMethodDataSourceCreate();
  9.     }
  10. }
No write() com o código:
  1. public void write()
  2. {
  3.     ttsbegin;
  4.     element.numberSeqFormHandler().formMethodDataSourceWrite();
  5.     super();
  6.     ttscommit;
  7. }
E no delete() como código:
  1. public void delete()
  2. {
  3.     element.numberSeqFormHandler().formMethodDataSourceDelete();
  4.     super();
  5. }
 E podemos testar nossa sequência numérica, que irá funcionar.

[]'s
Alexssander

Como trabalhar com relatórios via Reporting Services utilizando a query da AOT

Ontem o meu amigo Leandro Sales me perguntou como criar um relatório no Reporting Services utilizando a query da AOT, já havia postado no blog como chamar de dentro do Visual Studio uma classe do AX para retornar um buffer com os dados. Hoje vou postar como retornar os dados utilizando a quey criada na AOT.

Primeiro devemos criar nossa query, para exemplo vamos criar uma query bem simples, porém podemos utilizar todos os recursos do objeto query.

Objeto Query na AOT

Em seguida vamos criar um novo projeto no Visual Studio em C# com o template Dynamics AX Reporting Project.

Agora com o projeto criado vamos adicionar um novo DataSet e para esse DataSet vamos selecionar a query que criamos no AX, selecionar os campos que iremos utilizar.


Feito isso nossa estrutura do relatório ficará da seguinte maneira.

Estrutura do relatório no Visual Studio

Agora com os dados já filtrados e selecionados, podemos manipular os dados dentro do Visual Studio aproveitando todos os recursos que temos para criar o Design, Parâmetros, Filtros, Agrupamento e tudo mais.

Espero que tenha sido útil.

[]'s
Alexssander

Gerar um relatório a partir de um registro selecionado

Ontem meu amigo Abraão me pediu ajuda pra gerar um relatório de acordo com um registro selecionado em um formulário. E me lembrava que já tinha feito e que era muito simples, porém não me recordava dos detalhes. Então em uma breve conversa com meu amigo Leandro Honório ele me ajudou muito e concluí os detalhes e ajudei o Abraão a solucionar o problema. Como já tinha passado pela mesma situação e não guardei tomei nota da resolução, decidi postar dessa vez para termos mais um auxílio.

Nesse caso tenho o formulário AssetTable e preciso criar um botão no formulário que leve para um relatório, porém as informações do relatório devem ser baseadas no registro selecionado.

Primeiro criei um relatório simples com o DataSource AssetTable e no design adicionei 2 campos apenas para demonstrar, a estrutura do relatório ficou da seguinte maneira:

Estrutura do relatório

Em seguida declarei a tabela como uma variável global:
  1. public class ReportRun extends ObjectRun
  2. {
  3.     AssetTable currentRecord;
  4. }
Depois sobreescrevi o método init, para que no momento que ele iniciar o relatório nossa variável global receba os parâmetro do registro selecionado:
  1. public void init()
  2. {
  3.     super();
  4.     currentRecord = element.args().record();
  5. }
E em seguida vamos sobreescrever o método fetch, para executar nossa query e para que possamos adicionar o range atual ao DataSource, assim preenchendo o DataSource apenas com os dados do registro selecionado:
  1. public boolean fetch()
  2. {
  3. boolean ret;
  4. ;
  5.  
  6. queryRun = new QueryRun(this);
  7. queryRun.query().dataSourceTable(tablenum(AssetTable)).addRange(fieldnum(AssetTable, AssetId)).value(currentRecord.AssetId);
  8. ret = super();
  9. return ret;
  10. }
Feito isso vamos desabilitar a propriedade Interactive do Relatório e na Query.

Com o relatório criado vamos criar um MenuItem do nosso relatório e no nosso formulário criar um botão partindo do MenuItem e passar a propriedade DataSouce para a tabela que estamos utilizando.

E com isso ao selecionar um registro e clicarmos no botão o relatório é gerado a partir do registro selecionado.

[]'s
Alexssander

Checar permissão em um método display.

Olá,

Precisei fazer uma validação em um método display e dando uma olhada no material da Microsoft descobri exatamente o que preciso fazer e resolvi postar, pois muitas vezes esquecemos do que aprendemos nas apostilas.

Você precisa criar um método display no formulário de clientes que retorne o total do campo InvoiceAmount da tabela CustInvoiceJour. Se o usuário logado não tem acesso aos dados da determinada tabela, retornar 0.

Para isso vamos criar o seguinte método display na CustTable:
  1. display amountMST totalInvoiceAmount()
  2. {
  3.     CustInvoiceJour custInvoiceJour;
  4.     DictField dictField = new
  5.     DictField(tablenum(CustInvoiceJour), fieldNum(CustInvoiceJour, InvoiceAmount));
  6.     ;
  7.  
  8.     if (dictField.rights() >= AccessType::Add)
  9.     {
  10.         return (select sum(InvoiceAmount) from custInvoiceJour where custInvoiceJour.InvoiceAccount == this.AccountNum).InvoiceAmount;
  11.     }
  12.     return 0;
  13. }
Vamos arrastar o método criado para a grid Overview no formulário CustTable.
E nas propriedades do novo controle setar o DataSource para CustTable.
 
[]'s
Alexssander

Como associar uma classe do Dynamics AX ao dataSet do Visual Studio

Para adicionar uma consulta para AXQuery você passa em um dicionário de objetos, o nosso range precisa ser equivalente a:
  1. select * from InventTrans where DatePhysical < _paramDate && (DateFinancial == datenull() || DateFinancial > _paramDate)
O problema está em como fazer o OR no campo DateFinancial. Além disso, como você faz entre um intervalo, você pode ter apenas um campo no dicionário o que parece só permitir ..data ou data..
Para resolver esse problema fiz o seguinte:

1- Criei uma classe na AOT chamada InventTransReport, com o seguinte método:
  1. public InventTrans returnData(str parmDate)
  2. {
  3.       InventTrans InvTr;
  4.       date parm;
  5.       ;
  6.  
  7.       parm = str2date(parmDate,123);
  8.  
  9.       select * from InvTr
  10.         where (invTr.DateFinancial < parm)
  11.            && (invTr.DateFinancial == str2date("",123)
  12.            || invTr.DatePhysical > parm);
  13.  
  14.       return InvTr;
  15. }
2 - No Visual Studio eu criei um novo projeto usando Dynamics AX Reporting Project.

3 - Depois criei um novo dataMethod no relatório com o seguinte código:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Security.Permissions;
  4. using System.Data;
  5. using Microsoft.Dynamics.Framework.Reports;
  6. public partial class Report1
  7. {
  8.     [DataMethod(), AxSessionPermission(SecurityAction.Assert)]
  9.     public static DataTable ReportInventTrans(string paramDate)
  10.     {
  11.         // Call AX to get the report data
  12.         AxaptaWrapper ax = SessionManager.GetSession();
  13.         AxaptaObjectWrapper axClass = ax.CreateAxaptaObject("InventTransReport");
  14.         AxaptaRecordWrapper axRecord = ax.CreateAxaptaRecord(axClass.Call("returnData", paramDate));
  15.        
  16.         // Prepare the DataTable structure
  17.         DataTable resTable = new DataTable();
  18.         resTable.Columns.Add("ItemID", typeof(String));
  19.         resTable.Columns.Add("DatePhysical", typeof(DateTime));
  20.         resTable.Columns.Add("DateFinancial", typeof(DateTime));
  21.         while (axRecord.Found)
  22.         {
  23.             resTable.Rows.Add(axRecord.GetField("ItemID"), axRecord.GetField("DatePhysical"), axRecord.GetField("DateFinancial"));
  24.             axRecord.Next();
  25.         }
  26.         return resTable;
  27.  
  28.     }
  29. }
4 - Depois adicionar um novo DataSet para o meu relatório e alterar a propriedade:

Data Source Type: Business Logic
Query: ReportInventTrans


5 - Depois é só passar o parâmetro para a query, nesse caso estou passando uma data, mas poderia ser apenas *.


6 - Ao executar o relatório, basta passar o parâmetro.


[]'s
Alexssander

Cálculo de Fatorial

Olá,

Hoje o Francisco teve a brilhante idéia de reinventar a roda fazendo uma classe para calcular o Fatorial de um número.

Porém buscando no fantástico mundo do AX descobri que existe um método (factorial) na classe Global que faz exatamente o que ele precisa.

Abaixo um exemplo simples para quem não conhece como utilizar o método junto com a classe Dialog:
  1. static void factorial(Args _args)
  2. {
  3.     int         factorial;
  4.     Dialog      dialog;
  5.     DialogField dialogField;
  6.     ;

  7.     dialog = new Dialog("Cálculo de Fatorial");

  8.     dialogField = dialog.addField(typeId(Amount),"Digite o valor");
  9.     dialogField.value(factorial);

  10.     if(dialog.run())
  11.     {
  12.         factorial = dialogField.value();

  13.         info(strfmt("O fatorial é de %1 é: %2", factorial, num2str(Global::factorial(factorial),2,2,2,1)));
  14.     }
  15. }
[]'s
Alexssander

Criar um info ao iniciar o Dynamics AX

Para criarmos um infolog ao iniciarmos o AX basta alterar o método workspaceWindowCreated da classe Info.

Um exemplo, para obter o kernel:
  1. void workspaceWindowCreated(int _hWnd)
  2. {
  3.     // Put workspace window specific initialization here.
  4.     UserInfo userInfo;
  5.     str kernelVersion;
  6.     str applicationVersion;
  7.     str hotfixVersion;
  8.     ;
  9.  
  10.     kernelVersion = xInfo::releaseVersion() + '.' + this.getKernelBuildNo();
  11.     WinAPI::SetWindowText(_hWnd, strFmt("%1 - %2",WinAPI::getWindowText(_hWnd), xInfo::configuration()));
  12.  
  13.     ApplicationVersion = ApplicationVersion::buildNo();
  14.  
  15.     if(ApplicationVersion::GLSAppl()!='')
  16.     {
  17.         applicationVersion += ApplicationVersion::GLSAppl();
  18.     }
  19.     info(strfmt("%1 - %2 %3/ %4 %5", "Kernel", "Esperado:", "5.0.1500.1330", "Obtido:", kernelVersion));
  20.     info(strfmt("%1 - %2 %3/ %4 %5", "Application", "Esperado:", "5.0.1500.1313", "Obtido:", applicationVersion, hotfixVersion));
  21.  
  22.     select firstonly AutoLogOff
  23.         from userInfo
  24.             where userInfo.Id == curuserid();
  25.  
  26.     if (userInfo && userInfo.AutoLogOff)
  27.         this.setTimeOut(identifierstr(autologoff), userInfo.AutoLogOff*1000*60, true);
  28.  
  29.     this.setTimeOut(identifierstr(watchDog), #watchdogInterval, false);
  30. }

O resultado é o seguinte:
 
 
[]'s
Alexssander

Valores null para diversos tipos de dados

Para comparar tipos de dados com valores null:


O retorno dos dados é o seguinte:


[]'s
Alexssander

Gerar arquivo txt em formato ANSI ou Unicode?

Se você usar a classe TextIO você pode especificar uma página de códigos. O argumento codepage padrão corresponderá a arquivos que estão sendo escritas no formato Unicode.
Se você usar a classe AsciiIO você estará codificado em ACP (página de código ANSI).

file = new TextIO(_fileName, 'W');

file = new AsciiIO(_fileName, 'W');

[]'s
Alexssander

Preencher strings (direita e esquerda)

Frequentemente precisamos preencher alguma string com zeros a esquerda ou algo similar. Um lugar onde frequentemente vemos isto é quando vamos trabalhar com CNAB, onde devemos mandar X casas e só temos (X-5), ex.: Preciso mandar 000003, mas o número que tenho no AX é 3, e para isso, muitos (me incluo nestes) acabam reinventando a roda e fazendo loops para preencher e etc.

O uso é bem simples como podem ver abaixo:
  1. static void Job2(Args _args)
  2. {
  3.     int i = 1;
  4.     str formatedLeft, formatedRight;
  5.     str finalResultLeft, finalResultRight;
  6.     ;
  7.     formatedLeft = strRFix(int2str(i), 5, "0");
  8.     formatedRight = strLFix(int2str(i), 5, "0");
  9.     finalResultLeft = strFmt("NUM-%1", formatedLeft);
  10.     finalResultRight = strFmt("NUM-%1", formatedRight);
  11.     info(strFmt("%1 - %2", finalResultLeft, finalResultRight));
  12. }
[]'s
Alexssander

Como utilizar o Gravador de Tarefas (Task Recorder) no Dynamics AX

Muitas pessoas não conhecem, mas o AX possui uma ferramenta muito boa que é o Gravador de Tarefas (Task Recorder) e que pode ajudar muitos desenvolvedores e funcionais. Segue um tutorial de como utilizá-la.

Crie um documento word em branco e salvar.

Clique no item de menu Microsoft Dynamics AX -> Tools -> Task Recorder.


Clique no botão Start Recorder (vermelho) para iniciar a gravação.


Agora execute sua tarefa normalmente, como por exemplo um cadastro de Funcionário.


Após a execução, clique no botão Stop depois clique em Yes para salvar sua nova tarefa, nomeie sua tarefa e clique em Save.

Agora clique no botão Recorded Tasks, para exibir as tarefas salvas:


Selecione a tarefa que deseja gerar o documento e clique em Generate document e selecione o documento word que criamos para que ele seja gerado.


Depois clique em OK e o arquivo será gerado passo a passo com a tarefa que foi realizada.


O arquivo não fica 100%, pois se você faz algum passo errado ele também traz no documento, mas fazendo alguns ajustes no passo a passo vira uma documentação muito boa.

[]'s
Alexssander

Comparar um valor null utilizando a Classe SysQuery

Uma certa vez tive um problema ao tentar comparar um valor com null.
Para resolver utilizei a classe SysQuery da seguinte maneira:
  1. static void classFiscal(Args _args)
  2. {
  3.     InventTable inventTable;
  4.     str item;
  5.     ;
  6.     while select inventTable
  7.         where inventTable.TaxFiscalClassificationId == sysquery::valueEmptyString()
  8.     {
  9.         item = inventTable.ItemId + ' - ' + inventTable.TaxFiscalClassificationId;
  10.         info(item);
  11.     }
[]'s
Alexssander

Criar uma sequência numérica via X++

Há um tempo atrás precisei criar uma sequência numérica para ser impressa na geração de um arquivo .txt e para isso fiz o seguinte:

Criar uma seqüência numérica no caminho:

Básico > Configuração > Seqüências Numéricas > Seqüências Numéricas

Criei uma classe para a geração de arquivo .txt, no método que retorna os valores para a string inclui a chamada do método newGetNumFromCode que está na classe NumberSeq.
Para utilizar esse método passamos a sequência numérica que criamos no formulário de Seqüências Numéricas como argumento e ao gerar o arquivo ele retorna o próximo número da sequência.

str linha;
;
linha += strRFix(CompanyInfo::find().Name, 14, " "));
linha += NumberSeq::newGetNumFromCode('MinhaSeqNumerica').num();
linha + date2str(CompanyInfo::find().ConversionDate,123,2,0,2,0,2)
+ '\n';

[]'s
Alexssander

A diferença entre o AutoDesignSpecs para o Generated Design.

Quando você usa AutoDesignSpecs, o layout do relatório é gerado quando o relatório é executado. O seu conteúdo e o layout é baseado na consulta utilizado para o relatório.

No GeneratedDesign, o layout é ditado pela concepção da estrutura criada no Visual Report Designer e não por uma consulta ou modelo de relatório.

Um relatório que é criado usando a opção GeneratedDesignSpecs tem várias diferenças de uma criada usando AutoDesignSpecs.

• Uma diferença principal é que um design personalizado não é afetado pela estrutura da consulta subjacente. A concepção é definida pelo dono da obra e não gerado automaticamente.

• Outra diferença é que as alterações ao modelo de relatório padrão, se ela é usada, não afeta o desenho de um relatório usando a opção GeneratedDesignSpec.

[]'s
Alexssander

Bem vindos!

  1. static void Job1(Args _args)
  2. {
  3.     ;
  4.     info("Hello world!");
  5. }
Estou iniciando o blog a partir de hoje (ainda vou melhorar o layout e as informações) e espero expandir cada vez mais o conhecimento e poder compartilhar dúvidas, soluções sobre essa ótima ferramenta que é o Microsoft Dynamics AX e espero contar com a ajuda de todos também.

[]'s
Alexssander