D365 Finance – Erro na validação do Azure KeyVault

A partir da PU48 ou PU49, não sei qual exatamente, o código fonte da aplicação possui uma regra utilizando RegEx para validar o valor do campo Secret dos parametros do Azure KeyVault.

Então se você está encontrando algum problema na validação do seu certificado no formulário do Azure KeyVault, certifique-se que o campo secret dê Match com a expressão regular acima.

É aqui que o RegEx não dá Match: Método GetValue da classe KeyVaultClient:

Encontrei esse erro porque o campo Secret do AKV estava com o valor Vault:///TICATICA, sendo que o correto seria vault:///TICATICA, já que o RegEx é case sensitive.

Problema reproduzido num ambiente com as seguintes versões:

D365 Finance – Erro RetailServer em deploys de PU – Dev sandbox

Quando se deparar com o erro no script de RetailServer no deploy de pacote de Service Update (Platform Update), uma solução aplicável é fazer o seguinte:

“Under the deployable package folder, find the following SQL script <ServiceVolue>:\DeployablePackages\<PackageGUID>\RetailServer\Scripts\DropAllRetialChannelDbObjects.sql”

Executar o script SQL acima enquanto o ambiente está no status de “Failed”. Após a execução, retomar o deploy.

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

Gráficos!!!!

Bomm diaaa a todos. . .

Hoje vamos clarear um pouco a visão sobre gráficos no AX. Apenas um início para ter uma noção desta funcionalidade que é bem útil para visualização mais rápida de alguns indicadores.

Utilize o Form tutorial do AX para ter noção de como é o gráfico

  1. Abra a OAT
  2. Exploda o nó “Forms”
  3. Abra o formulário “Tutorial_Form_Graph”

Neste exemplo vamos utilizar o gráfico com o conceito de tabela temporária que foi explicado no post anterior.

Portanto, depois de criar a tabela, inserir os dados através do método CreateTableTemp(), vamos fazer carregamento do gráfico na tela.

São necessários mais 2 métodos alem do método de inserção de dados na tabela temporária.

Lembrando que no ClassDeclaration temos que descalarar uma variável do tipo “GRAPHICS” pois esta é a classe que carrega todas as cores, estilos, formas, grades e tudo que existe num gráfico. (Para saber mais, exploda o nó Classes na OAT e veja a classe GRAPHICS).

Bom, vamos começar do zero.

  • Criar a tabela temporária (post Anterior explicando o processo).
  • Criar um Novo Form
  • Arraste sua tabela para o DataSource do Form.
  • No Design crie um group com os campos que você deseja que apareça no gráfico.
  • No Design crie um novo Group do tipo ACTIVEX e selecione a opção “ChartObject”.
  • No design crie mais um group do tipo STATICTEXT e na propriedade AutoDeclaration Marque “YES”

Agora vamos para os métodos de criação do gráfico.

init();

√ Chama os métodos para carregar os dados na sua tabela temporária e chama o método para carregar os dados no gráfico e mostrar na tela. O “Description.text()” que você pode perceber no final do método é o Group do tipo STATICTEXT que foi falar no início do post, nele você pode escrever uma breve descrição da funcionalidade do gráfico.

updateGraph();

√ Instancia um objeto do tipo GRAPHICS. A linha “graphics.parmActiveX(graphControl);” é referente ao group do tipo ACTIVEX que foi falado também no início do post e “graphControl é o nome do group no design. Os outros são apenas para colocar títilo no gráfico, no eixo X e Y. O group ActiveX é pra ser do tipo “ChartObject” que é onde você estipula de o gráfico é 3D ou não, basta alternar o parametro para true ou false e observar a diferença ao abrir o gráfico. Logo abaixo, segue um while selec apenas para carregar os dados no grafico pelo método “loadData”. Depois basta chamar o método ShowGraph para que apareça na tela.

Bom, isso é o básico para se ter um gráfico.

Os dois métodos seguintes são apenas para fazer a atualização do gráfico sempre que for alterado algum registro na tabela.

Métodos Write e Delete.

Lembrando que são métodos de substituição e o código dentro deles é apenas:

element.updateGraph();

Apenas de grande, acredito que ficou fácil e compreensível.

Qualquer dúvida, crítica ou sugestão, deixe seu comentário.

Obrigado!

Tabela temporária

Boa Tarde,

Vamos falar um pouco sobre tabelas temporárias.

Para criar uma tabela temporária é bem simples, basta criar uma tabela comum na OAT e na propriedade “Temporary” deixar marcada a opção “YES“.

Pronto, está criada.

Geralmente utilizamos tabelas temporárias para fazer ações que não é necessário ficar com os dados no banco de dados, apenas carregamos os dados na tabela temporáriamente, ou seja,  quando for feita determinada transação ou coisa do tipo.

Portanto para inserir registros na tabela temporária é necessário utilizar um método por exemplo “CreateInventVend_W” seguindo o exemplo da figura acima.

Dentro desse método, você insere os registros que quiser, conforme figura exemplo abaixo.

Suponhamos que você colocou os EDT´s (ItemNum, Name e Qty) que são responsáveis por armazenar o código, nome e quantidade de um produto.

Após isso se você fizer um SELECT na tabela, os dados que foram inseridos serão encontrados, porém por ela ser uma tabela temporária se você abrí-la pela AOT não vai constar nenhum dado.

Obs: Por ser uma tabela temporária, ela existe apenas na OAT, portanto você não vai encontrar ela no SQLServer.

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 🙂