Páginas

Mostrando postagens com marcador Classes. Mostrar todas as postagens
Mostrando postagens com marcador Classes. Mostrar todas as postagens

Como enviar e-mail pelo AX sem configurar o Outlook

Essa semana precisei fazer o AX enviar e-mails direto, sem precisar configurar o Outlook.

Para isso verifiquei em alguns blogs de referência e no final criei um código simples e com algumas configurações do sistema fiz o envio diretamente pelo AX.

Antes de alterar qualquer código, vamos parametrizar a conta que o AX utiliza para fazer o envio, para isso vamos acessar o módulo Administração > Configuração > Parâmetros de email.
 

Nessa tela vamos configurar o servidor de saída, computador local, porta SMTP, tamanho do anexo e outras configurações como nome do usuário, senha e se iremos utilizar autenticação NTLM (que é um protocolo de autenticação para transações entre dois computadores em que um ou ambos estejam executando o Windows NT 4.0 ou anterior).

Ao enviar os e-mail eu não quero que apareça a conta que configuramos como remetente para executar o envio, para isso o usuário que está logado no sistema precisa ter a sua conta configurada nas opções de usuário.

Para configurar vamos no Painel de Navegação do Microsoft Dynamics AX > Ferramentas > Opções e vamos configurar a conta de email do usuário no campo Email:
 

Feito isso vamos alterar o código reportSendMail() da classe Info:
  1. void reportSendMail(PrintJobSettings p1)
  2. {
  3.     System.Net.Mail.MailMessage myMailMessage;
  4.     System.Net.Mail.Attachment attachment;
  5.     System.Net.Mail.AttachmentCollection attachementCollection;
  6.     System.Net.Mail.SmtpClient myMail;
  7.     System.Net.NetworkCredential myNC;
  8.     //Atribui o nome de usuário e senha dos parâmetros para as variáveis 
  9.     str myLogin = SysEmailParameters::find(true).SMTPUserName;
  10.     str myPassword = SysEmailParameters::password();
  11.     str userMailAddress; 
  12.     str receiverMailAddress;
  13.     str mailBody;
  14.     str smtpServer;
  15.     str mail;
  16.     str fileName = 'axaptareport';
  17.     FileNameOpen    fileNameForEmail; 
  18.     FileIOPermission perm;
  19.     ;
  20.     //Verifica o tipo do Anexo 
  21.     if (p1.format() == PrintFormat::ASCII)
  22.     {
  23.         fileNameForEmail = subStr(p1.fileName(),strLen(p1.fileName())-3,-999)+'TXT'; 
  24.     }
  25.     else if (p1.format() == PrintFormat::RTF)
  26.     {
  27.         fileNameForEmail = subStr(p1.fileName(),strLen(p1.fileName())-3,-999)+'RTF';
  28.     }
  29.     else if (p1.format() == PrintFormat::HTML)
  30.     {
  31.         fileNameForEmail = subStr(p1.fileName(),strLen(p1.fileName())-3,-999)+'HTM';
  32.     }
  33.     else if (p1.format() == PrintFormat::PDF || p1.format() == PrintFormat::PDF_EMBED_FONTS) 
  34.     {
  35.         fileNameForEmail = subStr(p1.fileName(),strLen(p1.fileName())-3,-999)+'PDF';
  36.     }
  37.     mail = subStr(fileNameforEmail,(strlen(fileNameforEmail)-8),9); 
  38.     fileNameforEmail = winApi::getTempPath() + mail;
  39.     perm = new FileIOPermission(fileNameforEmail,'w');
  40.     //Verifica se o usuário tem permissão 
  41.     if (!perm)
  42.     {
  43.         throw error("Não é possível mover o anexo para a pasta Temp.");
  44.         return;
  45.     }
  46.     else
  47.     {
  48.         try
  49.         {
  50.             perm.assert();
  51.         }
  52.         catch
  53.         {
  54.             throw error("Não é possível acessar a pasta Temp.");
  55.             return;
  56.         }
  57.         userMailAddress = SysUserInfo::find().Email;
  58.         receiverMailAddress = p1.mailTo() + "," + p1.mailCc();
  59.         mailBody = "E-mail enviado a partir da " + CompanyInfo::name() + ", pelo Microsoft Dynamics AX";
  60.         smtpServer = SysEmaiLParameters::find(false).SMTPRelayServerName;
  61.         myMailMessage = new System.Net.Mail.MailMessage(userMailAddress,receiverMailAddress);
  62.         myMailmessage.set_Subject(p1.mailSubject());
  63.         myMailmessage.set_Body(mailBody);
  64.         winapi::moveFile(p1.fileName(), fileNameforEmail);
  65.         attachementCollection = myMailMessage.get_Attachments();
  66.         attachment = new System.Net.Mail.Attachment(fileNameforEmail);
  67.         attachementCollection.Add(attachment);
  68.         myMail = new System.Net.Mail.SmtpClient(smtpServer);
  69.         myNC= new System.Net.NetworkCredential(mylogin,mypassword);
  70.         myMail.set_Credentials(myNC);
  71.         myMail.Send(myMailmessage);
  72.         Box::info("E-mail enviado com sucesso!");
  73.     }
  74. }
Para testar, vamos realizar o envio do e-mail de um relatório de clientes no módulo Contas a receber > Relatórios > Dados Básicos > Cliente


Filtrei apenas alguns clientes e agora vamos no botão Opções, selecionar a opção Destinatário do email, Preencher o assunto, destinatário, formato do anexo e depois Ok:


Você terá uma mensagem informando que o e-mail foi enviado com sucesso e receberá o e-mail com o relatório.
Note que o e-mail do remetente é o e-mail que configuramos nas opções do usuário.
E por fim temos o relatório do cliente em anexo.



Fiz um código meio que as pressas para isso e tenho certeza que dá para melhorar, cheguei a ver até que você pode configurar um template do corpo do email, por isso aceito sugestões e dúvidas.

Quando tiver um tempo vou dar uma olhada com calma nessa parte dos templates e crio um novo post futuramente.

[]'s
Alexssander

Como criar uma conexão entre o SQL e o Dynamics AX

Eu estou trabalhando em uma migração de dados um tanto extensa, mas ao invés de trabalhar exportando dados do antigo sistema para um arquivo e importando o mesmo para o AX, decidimos criar uma conexão direta com o SQL, ler as informações e importá-las para o AX. Meu amigo ucraniano que trabalha comigo Kirill Val me deu as instruções e depois da primeira classe o resto é brincadeira de criança. Gostaria de que caso alguém tenha alguma sugestão de melhoria, por favor envie.

Criei uma classe chamada AXImport e no método classDeclaration() eu declaro duas variáveis, uma do tipo LoginProperty e outra do tipo ODBCConnection. Essa classe específica é usada também para update, por isso, para executá-la em batch, eu a fiz extendendo a classe RunBaseBatch, mas não é uma regra.
  1. public class AXImport extends RunBaseBatch
  2. {
  3.     LoginProperty         loginProperty;
  4.     ODBCConnection  connection;
Depois crie um método connect() que faça a conexão com o banco:
  1. void connect()
  2. {
  3.     ;
  4.     loginProperty = new LoginProperty();
  5.     loginProperty.setServer('localhost');//Endereço do server, no caso era local
  6.     loginProperty.setDatabase('DATABASE');//Nome da base, na dúvida, cheque no SQL
  7.  
  8.     connection = new ODBCConnection(loginProperty);
  9. }
Crie os métodos pack() e unpack():
  1. public container pack()
  2. {
  3.     container ret;
  4.     return ret;
  5. }

  1. public boolean unpack(container packedClass)
  2. {
  3.     boolean ret = true;
  4.     return ret;
  5. }
Agora crie um método que leia os dados do banco e os insira no AX. Só vamos criar um método que vai inserir alguns fornecedores. Como é um exemplo, só vou mandar buscar o id, nome e grupo de fornecedores. Você pode colocar quais campos você quer no select, ou usar o SELECT * FROM, mas neste caso, você vai precisar ver no SQL qual vai ser a ordem em que os campos aparecem, enquanto na primeira opção você escolhe a ordem dos campos. Saber a ordem dos campos é importante na hora de determinar qual campo do banco preencherá o campo da tabela no AX.
  1. void TransferData()
  2. {
  3.     Statement statement;
  4.     ResultSet  resultSet;
  5.     str            sql;
  6.     str            description;
  7.     SqlStatementExecutePermission perm;
  8.     VendTable      vendTable;
  9.     AxVendTable axVendTable;
  10.     VendGroupId vendGroup;
  11.     VendAccount vendAccount;
  12.     VendName    vendName;
  13.     ;
  14.     //Deve ser utilizado a sintaxe do SQL, não a do AX.
  15.     sql = "SELECT NANUM, NANAME, NAVENDG FROM TABELA_EXEMPLO";
  16.     perm = new SqlStatementExecutePermission(sql);
  17.     perm.assert();
  18.  
  19.     statement = connection.createStatement();
  20.     resultSet  = statement.executeQuery(sql);
  21.  
  22.     while (resultSet.next())
  23.     {
  24.         //Na mesma ordem do select ou, se usado o select * from, na que está no SQL
  25.         vendAccount = strrtrim(resultSet.getString(1));
  26.         vendName = strrtrim(resultSet.getString(2));
  27.         vendGroup = strrtrim(resultSet.getString(3));
  28.         vendTable = VendTable::find(vendAccount);
  29.  
  30.         if (!vendTable)
  31.         {
  32.             axVendTable = new AxVendTable();
  33.             axVendTable.parmAccountNum(vendAccount);
  34.             axVendTable.parmVendGroup(vendGroup);
  35.             axVendTable.parmName(vendName);
  36.             axVendTable.save();
  37.             info("Inserido Fornecedor: " + vendAccount);
  38.         }
  39.     }
  40.     resultSet.close();
  41.     statement.close();
  42. }
Crie o método run() que vai chamar primeiro o método da conexão e depois o da transferência de dados.
  1. public void run()
  2. {
  3.     this.connect();
  4.     this.TransferData();
  5. }
Crie o método main():
  1. static void main(Args args)
  2. {
  3.     AXImport axImport = new AXImport();
  4.     ;
  5.     if (axImport.prompt())
  6.     {
  7.         axImport.run();
  8.         info("Importação concluída");
  9.     }
  10. }
Bom, essa é a classe de exemplo, qualquer sugestão de melhoria ou críticas são bem-vindas.

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 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

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

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