Gerando Hash SHA-1 a partir de uma String

Olá,

Algumas customizações referentes a Nota Fiscal Eletrônica de Serviços requerem que seja criada um hash SHA-1 em uma tag Assinatura.

Da primeira vez que tive que passar por esta situação foi bem complicado e levei muitas horas para fazer um algoritmo muito complexo… mas recentemente tive a oportunidade de colaborar neste assunto novamente e conseguimos chegar a um algoritmo muito mais simples.

Segue abaixo job que exemplifica o mesmo:

 

static void SHA_1()
{
System.Security.Cryptography.SHA1CryptoServiceProvider hash;
System.Text.ASCIIEncoding encoder;
System.Byte[] combined;
System.Byte byte;
str Sha1, input;
int i, arrayLength;
container cont;

;
new InteropPermission(InteropKind::ClrInterop).assert();

input = “00000317330NF 00000003866320090905T NN000000000001686000000000000000082997990008764130000102”;

hash = new System.Security.Cryptography.SHA1CryptoServiceProvider();
encoder = new System.Text.ASCIIEncoding();
combined = encoder.GetBytes(input);

hash.ComputeHash(combined);

sha1 = System.BitConverter::ToString(hash.get_Hash());

info(stralpha(sha1));
}

 

Espero que ajude e poupe um bom esforço e tempo…!!!

Créditos de colaboração ao sr. Hilquias Ferreira, futuro escritor deste blog =)

Trabalhando com datas no Microsoft Dynamics Ax

Muitas vezes precisamos trabalhar com datas no Ax.

Existem algumas funções que faciliam muito este trabalho.

Dica: O comando “Shift+F4” abre a lista de funções do Ax

Fiz um job exemplificando a utilização destas:

 

static void dateJob(Args _args)
{
Date birthday = 311\2013;
;
//Ano a partir da data
info(strfmt(“Ano: %1”, year(birthday)));

//Mês a partir da data
info(strfmt(“Mês (numeral): %1”, mthofyr(birthday)));

//Nome do mês
info(strfmt(“Mês (extenso): %1”, mthname(mthOfyr(birthday))));

//Próximo mês
info(strfmt(“Próximo mês: %1”, mthName(mthofyr(nextmth(birthday)))));

//Semana a partir da data
info(strfmt(“Semana: %1”, wkofyr(birthday)));

//Dia a partir da data
info(strFmt(“Dia: %1”, dayofmth(birthday)));

//Dia da semana (numeral)
info(strfmt(“Dia da semana (numeral): %1”, dayofwk(birthday)));

//Dia da semana (extenso)
info(strfmt(“Dia da semana (extenso): %1”, dayname(dayofwk(birthday))));

//Dias passados desde o início do ano
info(strfmt(“Dia do ano (conta quantos dias passaram): %1”, dayofyr(birthday)));
}

 

A saída deste job será a seguinte:

Imagem

Como pegar o valor anterior do campo durante uma modificação

As vezes, durante o desenvolvimento, é necessário verificar o valor anterior de um registro na tabela.

A primeira opção que muitos de nós pensamos seria declarar uma variável para guardar o valor, modifica-lo e depois fazer a comparação, porém as tabelas do Ax possuem um método que verifica o valor “commitado” do Table Buffer.

Este método é o orig().

Segue exemplo:

static void origMethod(Args _args)

{
CustTable ct;
;

select ct where ct.AccountNum == “000003”;

ct.AccountNum = “Modificado”;

info(strfmt(“Valor atual: %1, valor anterior: %2”,
ct.AccountNum,
ct.orig().AccountNum));
}

O info irá exibir a seguinte mensagem:

infolog

 

Erro nos índices das tabelas – Recriar SqlDictionary

Depois de bem mais de um ano sem fazer uma única postagem, venho postar algo de interesse para quem está passando por um problema chatíssimo: índices de campos.

Este script recria os registros dos Ids da tabela desejada que não se encontram na SqlDictionary.

Quem já teve a experiência de participar de uma migração, por exemplo, deve ter se deparado com este problema.

#macrolib.DictField
// This type is used instead of Types::Int64 for fields of type
// RecId/RefRecId/createdTransactionId/modifiedTransactionId
#define.RecIdBaseType   (49)
// For nonsystem fields of type UtcDateTime an additional field is created
// that holds the actual time zone in which the value has been set
#define.TZIDsuffix      ('_TZID')          

SqlDictionary   sqlDict;
SysdictType     dictType;
DictTable       dictTable;
DictField       dictField;
ArrayIdx        arrIdx;
Counter         numOfSqlFields; // number of records created in SqlDictionary
fieldName       fieldName;
fieldId         fieldId;
tableId         tableId = tablenum(TheTable2Fix);  // TARGET

boolean processTableField(
    DictField   _dictField,
    ArrayIdx    _arrIdx,
    boolean     _isTzIdField = false
    )
{
    ArrayIdx    dictArrIdx;
    str         infoName;       // this field name is for messages only
    FieldName   sqlName;
    boolean     ret;
    ;
    if (_isTzIdField)
    {
        if (    _dictField.baseType()   != Types::UtcDateTime
            ||  _dictField.id()         == fieldnum(Common, createdDateTime)
            ||  _dictField.id()         == fieldnum(Common, modifiedDateTime)
           )
        {
            throw error(Error::wrongUseOfFunction(funcname()));
        }
        dictArrIdx  = _dictField.arraySize() + _arrIdx;
        sqlName     = _dictField.dateTimeTimeZoneRuleFieldName(_arrIdx - 1);
        infoName    = _dictField.name() + #TZIDsuffix;
    }
    else
    {
        dictArrIdx  = _arrIdx;
        sqlName     = _dictField.name(DbBackend::Sql, _arrIdx);
        infoName    = _dictField.name();
    }
    select firstonly sqlDict
        where   sqlDict.tabId   == _dictField.tableid()
            &&  sqlDict.fieldId == _dictField.id()
            &&  sqlDict.array   == dictArrIdx
                ;
    if (!sqlDict)
    {
        sqlDict.clear();
        sqlDict.initValue();
        sqlDict.tabId           = _dictField.tableid();
        sqlDict.fieldId         = _dictField.id();
        sqlDict.array           = dictArrIdx;
        sqlDict.name            = strupr(_dictField.name(DbBackend::Native, _arrIdx));
        sqlDict.sqlName         = sqlName;
        dictType                = new SysDictType(_dictField.typeId());
        if (_isTzIdField)
        {
            sqlDict.fieldType   = Types::Integer;
        }
        else
        if (        _dictField.id()     == fieldnum(Common, RecId)
            ||      _dictField.id()     == fieldnum(Common, createdTransactionId)
            ||      _dictField.id()     == fieldnum(Common, modifiedTransactionId)
            ||      _dictField.typeId() == extendedtypenum(RecId)
            ||      _dictField.typeId() == extendedtypenum(RefRecId)
            ||  (   dictType
                &&  dictType.isExtending(extendedtypenum(RecId))
                )
           )
        {
            // This type is used instead of Types::Int64 for fields of type
            // RecId/RefRecId/createdTransactionId/modifiedTransactionId
            sqlDict.fieldType   = #RecIdBaseType;
        }
        else
        {
            sqlDict.fieldType   = _dictField.baseType();
        }
        sqlDict.strSize         = _dictField.stringLen();
        sqlDict.shadow          = bitTest(_dictField.flags(), #DBF_SHADOW);
        sqlDict.rightJustify    = bitTest(_dictField.flags(), #DBF_RIGHT);
        sqlDict.flags           = sqlDict.shadow;   // not _dictField.flags() at all!
        sqlDict.nullable        =   _dictField.baseType() == Types::Container
                                ||  _dictField.baseType() == Types::VarString
                                    ;
        if (sqlDict.validateWrite())
        {
            sqlDict.insert();
            ret = true;
            info(strfmt(@"Created record for field %1.%2%3",
                        dictTable.name(), infoName,
                        _dictField.arraySize() > 1 ? strfmt(@"[%1]", _arrIdx) : ''));
            // for all nonsystem UtcDateTime fields we also create a related TZID-field
            if (   !_isTzIdField
                &&  _dictField.baseType()   == Types::UtcDateTime
                &&  _dictField.id()         != fieldnum(Common, createdDateTime)
                &&  _dictField.id()         != fieldnum(Common, modifiedDateTime)
               )
            {
                processTableField(_dictField, _arrIdx, true);
            }
        }
        else
        {
            ret = checkFailed(strfmt(@"%1 record for %2.%3 was not created",
                                     tablestr(SqlDictionary), dictTable.name(), infoName));
        }
    }
    return ret;
}
;
dictTable = new DictTable(tableId);
if (!dictTable)
{
    throw error(strfmt(@"Failed to create %1 for '%2' (%3)",
                classstr(DictTable), tableid2name(tableId), tableId));
}
if (dictTable.isSystemTable())
{
    throw error(strfmt(@"'%1' is a system table, no way...", dictTable.name()));
}
if (!dictTable.isSql())
{
    throw error(strfmt(@"Table '%1' should not be in DB", dictTable.name()));
}
for (fieldId = dictTable.fieldNext(0); fieldId; fieldId = dictTable.fieldNext(fieldId))
{
    dictField = dictTable.fieldObject(fieldId);
    if (dictField && dictField.isSql())
    {
        fieldName = dictField.name();
        for (arrIdx = 1; arrIdx <= dictField.arraySize(); arrIdx++)
        {
            numOfSqlFields++;
            processTableField(dictField, arrIdx);
        }
    }
}
select firstonly sqlDict
    where   sqlDict.tabId   == tableId
        &&  sqlDict.fieldId == 0
            ;
if (!sqlDict)
{
    sqlDict.clear();
    sqlDict.initValue();
    sqlDict.tabId       = tableId;
    sqlDict.name        = strupr(dictTable.name());
    sqlDict.sqlName     = dictTable.name(DbBackend::Sql);
    sqlDict.strSize     = numOfSqlFields;       // for the table "header" - num of fields 
    sqlDict.flags       = dictTable.isView();   // that's the way it is
    sqlDict.insert();
    info(strfmt(@"Created record for table %1", dictTable.name()));
}

Créditos: http://gl00mie.blogspot.com.br/

 

Utilizando Tree Control – Parte II

Olá a todos…

Depois de um tempo em off, venho aqui postar uma continuação do meu post sobre o Tree Control.
Neste post explicarei como utilizar o Tree para manipular dados no Ax.

Só para relembrar: no post anterior, criamos uma tab para exibir a hierarquia de informações dos modelos de orçamento. Colocamos a lógica numa classe chamada “BudgetModelTree”.

Na classe criada para construir a tree, adicione ao ClassDeclaration:

image

Em seguida, adiciona os seguintes métodos à classe:

– CanMove

image

– Move

image

– stateDropHilite

image

– BeginDrag

image

– dragOver

image

– drop

image

Agora, vamos para o controle Tree no form. Abra a janela de propriedades e modifique a propriedade DragDrop para Manual.

image

Por fim, sobrescrevemos os métodos equivalentes na tree.

image

image

Se tudo deu certo, devemos conseguir alterar a base de dados por meio do tree.

“Isto tudo” é apenas o básico que se pode fazer no Ax. Existem outras maneiras de se utilizar o Tree Control. Para fins didáticos, eu criei uma tree com o número do cliente e nome dos clientes, as ordens de venda ligadas à ele e os itens de cada ordem, baseando-me no form Tutorial_Form_TreeControl. Mas isso já fica para um próximo post…

Espero que este post tenha sido útil!!!!

Colorindo registros no Form

Olá a todos os possíveis leitores deste blog….!

Venho hoje mostrar uma implementação bastante simples, que pode vir a ser útil em algum momento da sua carreira como desenvolvedor Dynamics Ax…

Desde os tempos das planilhas no excel, é possível colorir campos de acordo com uma condição(por exemplo, em uma planilha de notas escolares, as notas abaixo da média ficarem na cor vermelha). Podemos fazer a mesma coisa nos formulários do Ax.

Existe um método no datasource dos formulários que se chama “displayOption()”. Sobrescrevendo este método, podemos modificar as opções de exibição do registro no formulário.

Para demonstrar, usarei como exemplo o form “Detalhes da Ordem de Venda”, no módulo de contas a receber. Abra a AOT, e expanda os nós na seguinte ordem: Forms>SalesTable>DataSource>SalesTable>Methods. Sobrescreva o método “displayOption” com o seguinte código:

Código a ser inserido no método displayOption().

Desta maneira, as ordens de venda que estiverem com status “Em Aberto” ficarão a cor de fundo amarela.

Se tudo deu certo, o formulário deve ficar com esta aparência.

 

De dar inveja pra qualquer editor de planilha…. =)

Como funciona:

Esse método recebe dois argumentos: o primeiro é o registro atual, e o segundo é um objeto FormRowDisplayOption, cujas propriedades podem ser utilizadas para mudar as opções visuais do registro.

Espero que a demonstração tenha ficado clara e que todos tenham gostado do post!

P.S.: Se gostou, deixa um comentário pra gente 🙂

Tree Form Control

Tree é um controle de formulário que exibe em formato de árvore uma hierarquia de informações, tornando assim a visualização de informações muito mais clara e intuitiva para o usuário final. Como padrão no Ax, temos o form “Exibição da organização”, no módulo de RH.

Exemplo de tree no Ax

O tree possui algumas características:

  • Os nós da árvore são sempre criados via código, não por drag-and-drop;
  • A lógica de construção da árvore deve ser colocada em uma classe nova, para certificar que ela possa ser reutilizada e não interferir no código do form.

Demonstração

Para demonstrar a utilização deste controle, utilizarei o formulário “BudgetModel”.

Antes de criar a árvore, temos que criar a classe que a construirá. Na AOT, crie uma classe e a nomeie “BudgetModelTree”.
Em sua class declaration, declare:

Crie os seguintes métodos:

New:

constructor:

createNode:

BuildTree:

Agora com a classe criada, podemos inserir o controle no form.
Abra o formulário na AOT e crie uma nova TabPage. Nomeie-a TabTree. Nesta tabpage, adicione o controle Tree. Nomeie-o ModelTree, mude as propriedades Height e Width para Column height e column width.

Declare a classe no ClassDeclaration do form;


Sobrescreva o método init do form;


Na tabpage, sobrescreva o método pageActivated

Se tudo deu certo e sua tabela possuia dados, a tab deverá parecer com a imagem abaixo:

Resumo:
Para separar a árvore do resto do form, criamos uma nova TabPage, onde colocamos o TreeControl.
Os nós sempre são gerados por código;
A lógica de construção da árvore fica em uma classe;
Além dos métodos new() e construct(), a classe possui dois métodos que realmente geram a árvore:
createNode(), um método recursivo que gera os “galhos” do nó;
buildTree(), que constrói a árvore. Primeiro, deleta todos os nós e trava a atualização da árvore. Depois, adiciona os nós-pai e chamando o método acima. Após inserir todos os nós, destrava a árvore.
A geração da hierarquia de informações pode ser demorada, por isso só a criamos quando necessário, ou seja, quando a tabpage é aberta. Esse é o motivo de termos utilizado o método “pageActivated()”.

Arquitetura de camadas – Microsoft Dynamics Ax



Neste post irei falar um pouco sobre a arquitetura de camadas (layered architecture) do Ax.
Quando eu ouvi falar disso, logo me veio na mente “programação em três camadas”, padrão MVC, C#… mas não era nada disso.

Separar em camadas é o método utilizado pelo Ax para separar e controlar as atualizações e modificações feitas na aplicação. As camadas possuem níveis hierárquicos, de forma que você pode fazer alterações nos objetos sem que isso interfira nas camadas mais internas.

Arquitetura de camadas do Microsoft Dynamics Ax 2009

As camadas mais internas (brancas na figura) são de acesso restrito à microsoft. As mais externas (verdes na figura) são de acesso de parceiros e empresas. Isso será melhor discutido mais abaixo.
Quando você modifica um objeto, sua modificação passa a valer na aplicação. Caso você o delete, passa a valer a versão existente na camada abaixo dele.

Ex.: Certa classe foi criada na camada CUS. Então, na USR, você faz modificações no código, e o que passa a valer é a sua classe. Digamos que você acabe se arrependendo das mudanças. Se você deletar a classe, ela não vai sumir da AOT, e sim voltar ao estado da camada inferior (ou seja, como estava antes de qualquer modificação ser feita na USR).

E quais as vantagens desse tipo de arquitetura?

– Qualquer usuário do Microsoft Dynamics Ax pode customizar a aplicação;
– A aplicação padrão nunca é sobrescrita;
– Quando um objeto é deletado, ele é excluído da camada atual e das camadas mais externas.

As camadas são salvas em arquivos separadas com extensão .aod, sob o nome Axnomedacamada.aod. Aod é um acrônimo para “Application Object Dat file”. Isso se aplica até a versão atual, 2009.
Cada camada é de interesse de um grupo diferente de desenvolvedores.
As camadas mais internas, como citado anteriormente, são restritas à Microsoft.

– SYS: A camada mais interna do sistema. É onde a aplicação padrão do Microsoft Dynamics Ax é implementada.
– GLS: Como sabemos, o Ax é multi-línguas e multi-moedas. Nesta camada as funcionalidades específicas de cada localidade são aplicadas.
– HFX: A camada utilizada para hot-fixes por demanda.
– SL1, SL2 e SL3: Estas camadas são administradas por distribuidores e usadas para o desenvolvimento de soluções para parceiros verticais.

As camadas mais externas são acessadas por desenvolvedores ou usuários finais.

– BUS: Soluções criadas para distribuição vertical (clientes) e horizontal (outros parceiros) devem ser criadas na camada BUS.
– VAR: Parceiros de negócio devem criar as customizações de seus clientes nesta camada.
– CUS: Empresas que possuem um departamento de TI, se desejarem, podem criar para si mesmas customizações nesta camada.
– USR: Esta camada permite que cada filial de uma empresa crie customizações próprias.

Bom, este é um overview de como funciona a arquitetura em camadas no Microsoft Dynamics Ax 2009… acredito que exista pouco sobre esse assunto em português e acredito que seja uma boa ajuda a quem está começando a entender esta ferramenta.

Primeiro post

Olá…

Estou fazendo este post para apresentar a proposta do blog e me apresentar também…

Este blog foi formado hoje (vejam a data do post =P) com o intuito de compartilhar os conhecimentos de nossa equipe de desenvolvimento Ax e também criar uma base de dados sobre Ax em português, pois ainda são raros os artigos em nossa língua nativa…

E bom, sobre mim… sou Roberta Freitas (sim, uma menina!), tenho 21 anos, sou uma Student Partner da Microsoft e atualmente sou estagiária na Digisystem, na área de desenvolvimento para Microsoft Dynamics Ax.

Foi aqui que conheci essa tecnologia… me identifiquei e estou gostando de conhecer um pouquinho mais =) atualmente estou estudando para tirar certificações…

Espero poder compartilhar meus conhecimentos e aprender muito também =)