Novidades Carina? NDA Lifted?

by Pedro Azevedo 13. March 2015 00:57

Boas pessoal,

Hoje um post rápido para dar alguma informação do que está acontecer na comunidade, quem segue o twitter poderá ter visto novidades sobre a nova release do Dynamics CRM, denominada Carina. Associada a esta informação “NDA Lifted”.

Se eu tivesse visto isto a uns meses atrás não me dizia nada, por isso agora estou a partilhar com vocês o que isto quer dizer. Uma ou a mais importante vantagem de ser MVP é termos acesso antecipado as novidades e as versões do CRM mas como MVPs estamos ao abrigo do NDA (non-disclosure agreement) que nos proíbe de falarmos destas mesmas novidades e só quando a equipa do Dynamics CRM levanta o NDA é que podemos falar e publicar informações sobre as novidades.

Parece que alguém quebrou o NDA e publicou as novidades desta nova release este comportamento pode levar a expulsão desse mesmo membro.

Como podem imaginar brevemente surgirá um post sobre estas mesmo novidades Sorriso.

 

Até a próxima.

Tags: , ,

Como obter guids de registos II

by Pedro Azevedo 28. February 2015 04:44

Boas pessoal,

Voltando a um post antigo onde falo como obter os ids (guids) dos registos, hoje vamos ver como podemos obter guids das vistas e dos formulários e tentar perceber porquê podemos precisar destes guids.

Vistas

Vamos começar pelas vistas e começamos pela pergunta e porquê poderemos necessitar de obter o Id de uma vista:

  • Executar uma vista guardada, por exemplo quero saber as contas ativas, então posso executar a vista respetiva, as vistas são guardadas numa entidade chamada savedquery e que podemos usar por exemplo a mensagem ExecuteByIdSavedQueryRequest onde recebe o id desta vista.
  • Outra situação é se queremos abrir o formulário da vista das oportunidades para isso temos que usar o seguinte URL:
http://myorg.crm.dynamics.com/main.aspx?etn=opportunity&pagetype=entitylist&viewid=%7b00000000-0000-0000-00AA-000010003006%7d&viewtype=1039

Então a pergunta é, onde vamos buscar este id e a resposta é muito simples, teremos que ir a configuração da entidade, dentro das vistas, abrirmos uma vista e copiar o url:

image

https://psa201509.crm4.dynamics.com/tools/vieweditor/viewManager.aspx?appSolutionId=%7b76E811EE-F156-E511-80E1-3863BB345BD0%7d&entityId=%7b70816501-EDB9-4740-A16C-6A5EFBC05D84%7d&id=%7b00000000-0000-0000-00AA-000010001002%7d

Formulários

Nos formulários necessitamos por exemplo para abrir um registo num formulário específico, em vez de deixarmos o CRM escolher o melhor formulário para nós, vejam como podemos fazer isso:

var parameters = {};
parameters["formid"] = "b053a39a-041a-4356-acef-ddf00182762b";
parameters["name"] = "Test";
parameters["telephone1"] = "(425) 555-1234";
Xrm.Utility.openEntityForm("account", null, parameters);

Para obter é tudo semelhante ao feito na vista e para o resto dos componentes também:

image

https://psa201509.crm4.dynamics.com/main.aspx?appSolutionId=%7b76E811EE-F156-E511-80E1-3863BB345BD0%7d&etc=1&extraqs=formtype%3dmain%26formId%3d8448B78F-8F42-454E-8E2A-F8196B0419AF%26action%3d-1&pagetype=formeditor#44364656

Para um futuro post vou falar sobre o resto dos componentes, principalmente dos processos.

Até a próxima

Tags: ,

Pesquisa de códigos de erro

by Pedro Azevedo 16. February 2015 07:33

Boas pessoal,

O que eu mais gosto na comunidade é que colocam sempre desafios que me fazem pensar e perceber que ainda tenho muito para aprender. Quer dizer este post não está relacionado diretamente com uma pergunta  do fórum, tem a ver com a pergunta que respondi neste post. Onde o Dynamics CRM apenas era retorna o código de erro e não tínhamos qualquer descrição para tomarmos uma decisão.

Com este problema em mente decidi então criar uma biblioteca Javascript que permita essa pesquisa. Com base no número seja ele em inteiro ou hexadecimal, dar a mensagem correspondente a esse erro. Para além da biblioteca ainda criei um Recurso Web muito simples com numa caixa de texto para colocar o número de erro e chamando a biblioteca ele retorna a mensagem correspondente.

A solução deste problema foi bastante simples, foi importado o ficheiro xml de erros que vem junto do SDK como um Recurso Web e com o Javascript faço uma pesquisa nesse Recurso Web.

Em anexo estão os ficheiros da solução, brevemente espero colocar o código no github.

No futuro espero melhor este componente fazendo pesquisas nos fóruns de discussão do MSDN e Stackoverflow sobre esse erro, bem como arranjar uma maneira de mostrar o erro em várias línguas, algo que não consegui ainda realizar.

Updated: Atualizei as soluções para a versão mais recente.

Referências:

https://msdn.microsoft.com/en-us/library/gg328182.aspx

https://msdn.microsoft.com/en-us/library/gg328182%28v=crm.5%29.aspx

HelperErrorCodes_1_0_0_0_managed.zip (115,79 kb)

HelperErrorCodes_1_0_0_0_target_CRM_7.0_managed.zip (116,03 kb)

Tags: , , ,

CRM 2013 SP1 – UR2

by Pedro Azevedo 11. February 2015 13:39

Boas pessoal,

Foi disponibilizado o UR2 do CRM 2013 SP1 Este rollup pode-se transferir aqui. Para mais informações podem ver aqui, este rollup contém mais 100 issues resolvidos.

Até a próxima.

Tags: , , ,

Esconder opção do menu flutuante do Dynamics CRM

by Pedro Azevedo 28. January 2015 21:15

Boas pessoal,

Para mexermos no sitemap podemos usar a ferramenta SiteMapEditor eu aqui faço referência a esta ferramenta ou usarmos neste momento a minha favorita a XrmToolBox (prometo fazer uma série de posts a falar sobre os vários plugins desta ferramenta) que tem um plugin com o SiteMap Editor. Com estas ferramentas conseguimos criar mais áreas ou novos menus dentro de uma área.

Caso queiramos esconder um menu de um utilizador teremos que utilizar as roles para que esse utilizador não tenha acesso a essa entidade e assim ele não aparecer no menu flutuante.

E se quisermos esconder um menu com base num valor do formulário, por exemplo esconder as atividades quando o registo está num determinado estado, bem é o que faz o seguinte código:

function hideNavigationButton() {
    var menuSelect = window.top.$("#TabNode_tab0Tab");
 
    if(menuSelect.length == 0)    {
        setTimeout(function () { hideNavigationButton(); }, 1000);
        return;
    }
 
    menuSelect.on("mouseover", hideActivities);
}
 
function hideActivities() {
    var activities = window.top.$("#Node_navActivities");
    if(activities.length == 0) {
        setTimeout(function () { hideActivities(); }, 1000);
        return;
    }
    activities.parent().hide();
}

Neste caso não estou a verificar nenhum dado mas facilmente vocês colocam esse código, para isto funcionar terão que no OnLoad do formulário da entidade chamar o método hideNavigationButton.

Até a próxima.

Tags: , , ,

Alterar o owership de uma entidade após a criação

by Pedro Azevedo 13. January 2015 13:15

Boas pessoal,

Recentemente fui confrontado com uma situação de alterar o ownership de uma entidade já criada de organização para utilizador. Bom se estou com trabalho a escrever um post devem imaginar que não é tão simples como ir a entidade e mudar esta propriedades, pois não esta é uma daquelas propriedades que depois da entidade criada já não dá para editar, bom a primeira solução poderia passar em apagar a entidade criada e criar outra já com esta propriedade correta o problema é que já tinha muitas customizações, bem como alguns formulários, por isso teria que seguir outro caminho.

Então fui explorar se conseguia modificar o XML da solução e assim fiz. Inspirado por um post recente do Hosk que explica muito bem esta problemática, ele também propõe uma solução que seria criar uma nova entidade com propriedade utilizador\equipa e transpor todas as customizações já efetudas na entidade de organização para esta nova entidade. O meu objetivo será mostrar quais as diferenças no ficheiro customizations.xml entre uma entidade em que a propriedade seja organização e outra utilizador\equipa.

Primeiro é necessário uma solução que contenha a entidade de propriedade Organização e que queremos transformar numa entidade de propriedade Utilizador\Equipa. Exportamos a solução e depois de descompactar abrimos o ficheiro customizations.xml e façam as seguintes alterações:

Primeiro vamos identificar onde está declarado que a entidade está com propriedade, Organização e está neste atributo:

<OwnershipTypeMask>OrgOwned</OwnershipTypeMask>

Vamos substituir o valor por UserOwned ficando o atributo com este aspeto:

<OwnershipTypeMask>UserOwned</OwnershipTypeMask>

Agora necessitamos de procurar os campos que terão que existir para suportar esta alteração, no xml e dentro da entidade que está com a propriedade Organização existe um nó com esta estrutura:

<attribute PhysicalName="OrganizationId">
  <Type>lookup</Type>
  <Name>organizationid</Name>
  <LogicalName>organizationid</LogicalName>
  <RequiredLevel>none</RequiredLevel>
  <ImeMode>auto</ImeMode>
  <ValidForReadApi>1</ValidForReadApi>
  <IsCustomField>0</IsCustomField>
  <IsAuditEnabled>1</IsAuditEnabled>
  <IsSecured>0</IsSecured>
  <IntroducedVersion>1.0.0.0</IntroducedVersion>
  <IsCustomizable>1</IsCustomizable>
  <IsRenameable>1</IsRenameable>
  <CanModifySearchSettings>1</CanModifySearchSettings>
  <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
  <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings>
  <SourceType>0</SourceType>
  <ReferencedEntityObjectTypeCode>1019</ReferencedEntityObjectTypeCode>
  <LookupTypes />
  <displaynames>
    <displayname description="ID da Organização" languagecode="2070" />
  </displaynames>
  <Descriptions>
    <Description description="Identificador exclusivo para a organização" languagecode="2070" />
    <Description description="Identificador exclusivo da organização" languagecode="1046" />
  </Descriptions>
</attribute>

Devem substituir este atributo pelos 5 atributos a seguir para que seja possível colocar um utilizador ou equipa como proprietário de um registo desta entidade:

<attribute PhysicalName="OwnerId">
  <Type>owner</Type>
  <Name>ownerid</Name>
  <LogicalName>ownerid</LogicalName>
  <RequiredLevel>systemrequired</RequiredLevel>
  <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid|RequiredForForm</DisplayMask>
  <ImeMode>auto</ImeMode>
  <ValidForUpdateApi>1</ValidForUpdateApi>
  <ValidForReadApi>1</ValidForReadApi>
  <ValidForCreateApi>1</ValidForCreateApi>
  <IsCustomField>0</IsCustomField>
  <IsAuditEnabled>1</IsAuditEnabled>
  <IsSecured>0</IsSecured>
  <IntroducedVersion>1.0.0.0</IntroducedVersion>
  <IsCustomizable>1</IsCustomizable>
  <IsRenameable>1</IsRenameable>
  <CanModifySearchSettings>1</CanModifySearchSettings>
  <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
  <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings>
  <SourceType>0</SourceType>
  <LookupStyle>single</LookupStyle>
  <LookupTypes>
    <LookupType id="00000000-0000-0000-0000-000000000000">8</LookupType>
    <LookupType id="00000000-0000-0000-0000-000000000000">9</LookupType>
  </LookupTypes>
  <displaynames>
    <displayname description="Proprietário" languagecode="2070" />
  </displaynames>
  <Descriptions>
    <Description description="ID do Proprietário" languagecode="2070" />
  </Descriptions>
</attribute>
 
<attribute PhysicalName="OwnerIdName">
  <Type>nvarchar</Type>
  <Name>owneridname</Name>
  <LogicalName>owneridname</LogicalName>
  <RequiredLevel>systemrequired</RequiredLevel>
  <ImeMode>auto</ImeMode>
  <ValidForReadApi>1</ValidForReadApi>
  <IsCustomField>0</IsCustomField>
  <IsAuditEnabled>0</IsAuditEnabled>
  <IsLogical>1</IsLogical>
  <IsSecured>0</IsSecured>
  <IntroducedVersion>1.0.0.0</IntroducedVersion>
  <IsCustomizable>1</IsCustomizable>
  <IsRenameable>1</IsRenameable>
  <CanModifySearchSettings>1</CanModifySearchSettings>
  <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
  <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings>
  <SourceType>0</SourceType>
  <Format>text</Format>
  <MaxLength>100</MaxLength>
  <Length>320</Length>
  <Descriptions>
    <Description description="Nome do proprietário" languagecode="2070" />
  </Descriptions>
</attribute>
 
<attribute PhysicalName="OwnerIdType">
  <Type>int</Type>
  <Name>owneridtype</Name>
  <LogicalName>owneridtype</LogicalName>
  <RequiredLevel>systemrequired</RequiredLevel>
  <DisplayMask>ObjectTypeCode</DisplayMask>
  <ImeMode>disabled</ImeMode>
  <ValidForUpdateApi>1</ValidForUpdateApi>
  <ValidForReadApi>1</ValidForReadApi>
  <ValidForCreateApi>1</ValidForCreateApi>
  <IsCustomField>0</IsCustomField>
  <IsAuditEnabled>1</IsAuditEnabled>
  <IsSecured>0</IsSecured>
  <IntroducedVersion>1.0.0.0</IntroducedVersion>
  <IsCustomizable>1</IsCustomizable>
  <IsRenameable>1</IsRenameable>
  <CanModifySearchSettings>1</CanModifySearchSettings>
  <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
  <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings>
  <SourceType>0</SourceType>
  <Format></Format>
  <MinValue>-2147483648</MinValue>
  <MaxValue>2147483647</MaxValue>
  <Descriptions>
    <Description description="Tipo de ID de Proprietário" languagecode="1046" />
    <Description description="Tipo de ID do Proprietário" languagecode="2070" />
  </Descriptions>
</attribute>
 
<attribute PhysicalName="OwnerIdYomiName">
  <Type>nvarchar</Type>
  <Name>owneridyominame</Name>
  <LogicalName>owneridyominame</LogicalName>
  <RequiredLevel>systemrequired</RequiredLevel>
  <ImeMode>auto</ImeMode>
  <ValidForReadApi>1</ValidForReadApi>
  <IsCustomField>0</IsCustomField>
  <IsAuditEnabled>0</IsAuditEnabled>
  <IsLogical>1</IsLogical>
  <IsSecured>0</IsSecured>
  <IntroducedVersion>1.0.0.0</IntroducedVersion>
  <IsCustomizable>1</IsCustomizable>
  <IsRenameable>1</IsRenameable>
  <CanModifySearchSettings>1</CanModifySearchSettings>
  <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
  <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings>
  <SourceType>0</SourceType>
  <Format>text</Format>
  <MaxLength>100</MaxLength>
  <Length>320</Length>
  <YomiOf>OwnerIdName</YomiOf>
  <Descriptions>
    <Description description="Nome Yomi do proprietário" languagecode="1046" />
    <Description description="Nome yomi do proprietário" languagecode="2070" />
  </Descriptions>
</attribute>
 
<attribute PhysicalName="OwningBusinessUnit">
  <Type>lookup</Type>
  <Name>owningbusinessunit</Name>
  <LogicalName>owningbusinessunit</LogicalName>
  <RequiredLevel>none</RequiredLevel>
  <ImeMode>auto</ImeMode>
  <ValidForReadApi>1</ValidForReadApi>
  <IsCustomField>0</IsCustomField>
  <IsAuditEnabled>0</IsAuditEnabled>
  <IsSecured>0</IsSecured>
  <IntroducedVersion>1.0.0.0</IntroducedVersion>
  <IsCustomizable>1</IsCustomizable>
  <IsRenameable>1</IsRenameable>
  <CanModifySearchSettings>1</CanModifySearchSettings>
  <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
  <CanModifyAdditionalSettings>1</CanModifyAdditionalSettings>
  <SourceType>0</SourceType>
  <ReferencedEntityObjectTypeCode>10</ReferencedEntityObjectTypeCode>
  <LookupStyle>single</LookupStyle>
  <LookupTypes />
  <displaynames>
    <displayname description="Unidade de Negócio Proprietária" languagecode="2070" />
    <displayname description="Unidade de Negócios Proprietária" languagecode="1046" />
  </displaynames>
  <Descriptions>
    <Description description="Identificador exclusivo da unidade de negócios proprietária do registro" languagecode="1046" />
    <Description description="Identificador rtwexclusivo para a unidade de negócio proprietária do registo" languagecode="2070" />
  </Descriptions>
</attribute>

Criados os campos agora necessitamos de criar as relações para as entidades respetivas, aqui temos que ter cuidado com determinados valores (está assinalado amarelo os valores que temos que ter em consideração), a primeira coisa a fazer é procurar a relação entre a nossa entidade e a entidade Organização que tem a seguinte estrutura:

<EntityRelationship Name="organization_new_entityorganization">
  <EntityRelationshipType>OneToMany</EntityRelationshipType>
  <IsCustomizable>1</IsCustomizable>
  <IntroducedVersion>1.0.0.0</IntroducedVersion>
  <IsHierarchical>0</IsHierarchical>
  <ReferencingEntityName>new_entityorganization</ReferencingEntityName>
  <ReferencedEntityName>Organization</ReferencedEntityName>
  <CascadeAssign>NoCascade</CascadeAssign>
  <CascadeDelete>NoCascade</CascadeDelete>
  <CascadeReparent>NoCascade</CascadeReparent>
  <CascadeShare>NoCascade</CascadeShare>
  <CascadeUnshare>NoCascade</CascadeUnshare>
  <ReferencingAttributeName>OrganizationId</ReferencingAttributeName>
  <RelationshipDescription>
    <Descriptions>
      <Description description="Identificador exclusivo para a organização" languagecode="2070" />
      <Description description="Identificador exclusivo da organização" languagecode="1046" />
    </Descriptions>
  </RelationshipDescription>
  <field name="organizationid" requiredlevel="none" imemode="auto" format="">
    <IsCustomizable>1</IsCustomizable>
    <IsRenameable>1</IsRenameable>
    <CanModifySearchSettings>1</CanModifySearchSettings>
    <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
    <IsSecured>0</IsSecured>
    <IsAuditEnabled>1</IsAuditEnabled>
    <displaynames>
      <displayname description="ID da Organização" languagecode="2070" />
    </displaynames>
  </field>
</EntityRelationship>

Temos que substituir a relação acima com as seguintes relações, atenção que novamente amarelo estão valores que terão que modificar mediante a vossa realidade:

<EntityRelationship Name="owner_new_entityorganization">
  <EntityRelationshipType>OneToMany</EntityRelationshipType>
  <IsCustomizable>1</IsCustomizable>
  <IntroducedVersion>1.0.0.0</IntroducedVersion>
  <IsHierarchical>0</IsHierarchical>
  <ReferencingEntityName>new_entityorganization</ReferencingEntityName>
  <ReferencedEntityName>Owner</ReferencedEntityName>
  <CascadeAssign>NoCascade</CascadeAssign>
  <CascadeDelete>NoCascade</CascadeDelete>
  <CascadeReparent>NoCascade</CascadeReparent>
  <CascadeShare>NoCascade</CascadeShare>
  <CascadeUnshare>NoCascade</CascadeUnshare>
  <ReferencingAttributeName>OwnerId</ReferencingAttributeName>
  <RelationshipDescription>
    <Descriptions>
      <Description description="ID do Proprietário" languagecode="2070" />
    </Descriptions>
  </RelationshipDescription>
  <field name="ownerid" requiredlevel="systemrequired" imemode="auto" lookupstyle="single" lookupbrowse="0" format="" lookuptypes="8, 9">
    <IsCustomizable>1</IsCustomizable>
    <IsRenameable>1</IsRenameable>
    <CanModifySearchSettings>1</CanModifySearchSettings>
    <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
    <IsSecured>0</IsSecured>
    <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid|RequiredForForm</DisplayMask>
    <IsAuditEnabled>1</IsAuditEnabled>
    <displaynames>
      <displayname description="Proprietário" languagecode="2070" />
    </displaynames>
  </field>
</EntityRelationship>
 
<EntityRelationship Name="team_new_entityorganization">
  <EntityRelationshipType>OneToMany</EntityRelationshipType>
  <IsCustomizable>1</IsCustomizable>
  <IntroducedVersion>1.0.0.0</IntroducedVersion>
  <IsHierarchical>0</IsHierarchical>
  <ReferencingEntityName>new_entityorganization</ReferencingEntityName>
  <ReferencedEntityName>Team</ReferencedEntityName>
  <CascadeAssign>NoCascade</CascadeAssign>
  <CascadeDelete>NoCascade</CascadeDelete>
  <CascadeReparent>NoCascade</CascadeReparent>
  <CascadeShare>NoCascade</CascadeShare>
  <CascadeUnshare>NoCascade</CascadeUnshare>
  <ReferencingAttributeName>OwningTeam</ReferencingAttributeName>
  <RelationshipDescription>
    <Descriptions>
      <Description description="Identificador exclusivo para a equipa proprietária do registo." languagecode="2070" />
      <Description description="Identificador exclusivo da equipe que possui o registro." languagecode="1046" />
    </Descriptions>
  </RelationshipDescription>
  <field name="owningteam" requiredlevel="none" imemode="auto" lookupstyle="single" lookupbrowse="0" format="">
    <IsCustomizable>1</IsCustomizable>
    <IsRenameable>1</IsRenameable>
    <CanModifySearchSettings>1</CanModifySearchSettings>
    <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
    <IsSecured>0</IsSecured>
    <IsAuditEnabled>0</IsAuditEnabled>
    <displaynames>
      <displayname description="Equipa Proprietária" languagecode="2070" />
      <displayname description="Equipe Proprietária" languagecode="1046" />
    </displaynames>
  </field>
</EntityRelationship>
 
<EntityRelationship Name="user_new_entityorganization">
  <EntityRelationshipType>OneToMany</EntityRelationshipType>
  <IsCustomizable>1</IsCustomizable>
  <IntroducedVersion>1.0.0.0</IntroducedVersion>
  <IsHierarchical>0</IsHierarchical>
  <ReferencingEntityName>new_entityorganization</ReferencingEntityName>
  <ReferencedEntityName>SystemUser</ReferencedEntityName>
  <CascadeAssign>NoCascade</CascadeAssign>
  <CascadeDelete>NoCascade</CascadeDelete>
  <CascadeReparent>NoCascade</CascadeReparent>
  <CascadeShare>NoCascade</CascadeShare>
  <CascadeUnshare>NoCascade</CascadeUnshare>
  <ReferencingAttributeName>OwningUser</ReferencingAttributeName>
  <RelationshipDescription>
    <Descriptions>
      <Description description="Identificador exclusivo para o utilizador proprietário do registo." languagecode="2070" />
      <Description description="Identificador exclusivo do usuário proprietário do registro." languagecode="1046" />
    </Descriptions>
  </RelationshipDescription>
  <field name="owninguser" requiredlevel="none" imemode="auto" lookupstyle="single" lookupbrowse="0" format="">
    <IsCustomizable>1</IsCustomizable>
    <IsRenameable>1</IsRenameable>
    <CanModifySearchSettings>1</CanModifySearchSettings>
    <CanModifyRequirementLevelSettings>1</CanModifyRequirementLevelSettings>
    <IsSecured>0</IsSecured>
    <IsAuditEnabled>0</IsAuditEnabled>
    <displaynames>
      <displayname description="Utilizador Proprietário" languagecode="2070" />
      <displayname description="Usuário Proprietário" languagecode="1046" />
    </displaynames>
  </field>
</EntityRelationship>

Estas são as alterações que tem que fazer para mudar a propriedade de uma entidade.

Até a próxima.

Tags: , ,

Limpar contactos ou contas de atividade marketing falhadas

by Pedro Azevedo 11. December 2014 11:10

Boas pessoal,

Fui deparado com um problema que apareceu no fórum do Dynamics CRM em português. O objetivo do desafio passava por ter uma maneira mais expedita para eliminar\desativar contas ou contactos que tenham dado erro aquando de uma distribuição de uma campanha.

O primeiro desafio foi obter estes erros. Na interface gráfica os erros aparecem no seletor “Falhas”. Então a minha primeira tentativa foi através da interface REST e sobre a entidade CampaignActivity (Atividades de Campanha) mas sem sucesso porque aqui não aparecem os erros.

 

Depois de ver melhor a imagem reparei que a vista se chama “Vista associada de falhas de operações em massa”. Logo fui estudar a entidade BulkOperation mas aqui mais uma vez só encontrei a atividade chamada “Distribuição de Email” e não as atividades geradas. Depois de ver todas as relações da entidade, cheguei a entidade BulkOperationLog esta sim tem todas as atividades que foram geradas, as que correram bem e as que deram erro. Como não existe muita informação sobre esta entidade, executei uma chamada para ver o esquema desta entidade:

Esta entidade tem um campo o ErrorNumber que se tiver valor diferente de 0 é porque esta atividade deu erro. Infelizmente só tem mesmo o número do erro e não tem qualquer descrição. Bem este foi outro desafio. Eu já tinha escrito um post sobre erros e explicado como chegar a descrição. Para já vai ficar com o número do erro mas é meu objetivo de criar algo para me dar também a descrição do erro.

Tendo então descoberto qual a entidade que tinha a informação que necessitava e para além do erro ainda possuí qual o cliente\contacto através do campo RegardingObjectId. Podem ver a pré-visualização do resultado final:

Para listar esta informação utilizei um plugin jQuery chamado jqGrid e usei esta versão, já que a versão que tinha utilizado até aqui passou a ter uma licença paga. Para aqui também já evoluí este componente do jqGrid para facilitar listar entidades do CRM, mais tarde vou escrever um post sobre este componente.

Falando mais concretamente da implementação a primeira coisa foi colocar um botão ao nível da atividade de campanha. Botão este que irá chamar um recurso web html, este botão foi criado mais uma vez com recurso a ferramenta Ribbon Workbench:

Este botão vai chamar uma função Javascript onde é passado qual a campanha que estamos a falar neste momento e com isso mostrar todas as atividades que falharam:

function openWebResource(campaignId) {
    var customParameters = encodeURIComponent("?campaignId=" + campaignId);
    Xrm.Utility.openWebResource("new_failedcampaign/failedcampaign.html", customParameters, 300,300);
}

Este recurso web tem o plugin jqGrid e a sua inicialização, o método fetchGridData vai ser responsável por ir obter as atividades com falha e preencher a tabela.

$("#jqGrid").jqGrid({
        datatype: "local",
        height: '100%',
        colModel: [ {label: 'Id Registo', name: 'ObjectId', hidden: true},
                    {label: 'Tipo Registo', name: 'TypeRegister', width: 90},
                    {label: 'Nome do Registo', name: 'NomeRegisto', width: 200},
                    {label: 'Erro', name: 'ErrorNumber', width: 140 }
        ],
        viewrecords: true, // show the current page, data rang and total records on the toolbar
        caption: 'Carregar Atividades de Campanha Falhadas',
        pager: "#jqGridPager",
        gridComplete: initGrid
    });

    fetchGridData();
});

function fetchGridData() {
    var campaignActivityId;
    if (location.search != "") {
        vals = decodeURIComponent(location.search).split("=");
        campaignActivityId = vals[2].toString().split(',');
    }

    var gridArrayData = [];
    var odataquery = Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc";
    odataquery += "/CampaignActivitySet(guid'" + campaignActivityId + "')";
    odataquery += "?$expand=CampaignActivity_BulkOperations";
    odataquery += "&$select=CampaignActivity_BulkOperations/ActivityId";

    $.getJSON(odataquery, function(data) {
        if(data.d.CampaignActivity_BulkOperations) {
            var bulkOperations = data.d.CampaignActivity_BulkOperations.results;
            for (var idxBulkOper = 0; idxBulkOper < bulkOperations.length; ++idxBulkOper) {
                var activityId = bulkOperations[idxBulkOper].ActivityId;

                var odataquery2 = Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc";
                odataquery2 += "/BulkOperationSet(guid'" + activityId + "')";
                odataquery2 += "?$expand=BulkOperation_logs";
                odataquery2 += "&$select=BulkOperation_logs/ErrorNumber,BulkOperation_logs/RegardingObjectId";

                $.getJSON(odataquery2, function(data) {
                    var bulkLogs = data.d.BulkOperation_logs.results;

                    for (var i = 0; i < bulkLogs.length; i++) {
                        gridArrayData.push({
                            ObjectId: bulkLogs[i].RegardingObjectId.Id,
                            TypeRegister: bulkLogs[i].RegardingObjectId.LogicalName,
                            NomeRegisto: bulkLogs[i].RegardingObjectId.Name,
                            ErrorNumber: bulkLogs[i].ErrorNumber
                        });
                    };

                    $("#jqGrid").jqGrid('setGridParam', { data: gridArrayData});
                    $("#jqGrid").trigger('reloadGrid');
                });
            }
        }
    });
}

Quero realçar o seguinte código:

var odataquery = Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc";
odataquery += "/CampaignActivitySet(guid'" + campaignActivityId + "')";
odataquery += "?$expand=CampaignActivity_BulkOperations";
odataquery += "&$select=CampaignActivity_BulkOperations/ActivityId";

Este código vai obter todas as atividade de campanha e sobre cada uma obtenho os erros que aconteceram, como se pode ver a seguir:

var odataquery2 = Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc";
odataquery2 += "/BulkOperationSet(guid'" + activityId + "')";
odataquery2 += "?$expand=BulkOperation_logs";
odataquery2 += "&$select=BulkOperation_logs/ErrorNumber,BulkOperation_logs/RegardingObjectId";

Sobre o plugin jqGrid usei a extensão Context Menu para poder realizar ações sobre os registos listados, entre eles o inativar e o apagar. Para inativar usei a mensagem SetStateRequest, vejam aqui a função que utilizei:

function SetStateRequest(active, entity) {
    var grid = $("#jqGrid");
    var rowKey = grid.getGridParam("selrow");
    var rowData = grid.getRowData(rowKey);
    var entityId = rowData.ObjectId;

    var state, status;
    if(active){
        state = 0;
        status = 1;
    }
    else{
        state = 1;
        status = 2;
    }

   var requestMain = ""
   requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
   requestMain += "  <s:Body>";
   requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
   requestMain += "      <request i:type=\"b:SetStateRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";
   requestMain += "        <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
   requestMain += "          <a:KeyValuePairOfstringanyType>";
   requestMain += "            <c:key>EntityMoniker</c:key>";
   requestMain += "            <c:value i:type=\"a:EntityReference\">";
   requestMain += "              <a:Id>" + entityId + "</a:Id>";
   requestMain += "              <a:LogicalName>" + entity + "</a:LogicalName>";
   requestMain += "              <a:Name i:nil=\"true\" />";
   requestMain += "            </c:value>";
   requestMain += "          </a:KeyValuePairOfstringanyType>";
   requestMain += "          <a:KeyValuePairOfstringanyType>";
   requestMain += "            <c:key>State</c:key>";
   requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
   requestMain += "              <a:Value>" + state + "</a:Value>";
   requestMain += "            </c:value>";
   requestMain += "          </a:KeyValuePairOfstringanyType>";
   requestMain += "          <a:KeyValuePairOfstringanyType>";
   requestMain += "            <c:key>Status</c:key>";
   requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
   requestMain += "              <a:Value>" + status + "</a:Value>";
   requestMain += "            </c:value>";
   requestMain += "          </a:KeyValuePairOfstringanyType>";
   requestMain += "        </a:Parameters>";
   requestMain += "        <a:RequestId i:nil=\"true\" />";
   requestMain += "        <a:RequestName>SetState</a:RequestName>";
   requestMain += "      </request>";
   requestMain += "    </Execute>";
   requestMain += "  </s:Body>";
   requestMain += "</s:Envelope>";
   var req = new XMLHttpRequest();
   req.open("POST", _getServerUrl(), true)
   // Responses will return XML. It isn't possible to return JSON.
   req.setRequestHeader("Accept", "application/xml, text/xml, */*");
   req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
   req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
   var successCallback = null;
   var errorCallback = null;
   req.onreadystatechange = function () { SetStateResponse(req, successCallback, errorCallback); };
   req.send(requestMain);
}

Para apagar o registo usei a interface REST:

function DeleteEntity(accountId, entity) {
    var serverUrl = Xrm.Page.context.getClientUrl();
    var ODATA_ENDPOINT = "/XRMServices/2011/OrganizationData.svc/" + entity + "Set";
    var ODataPath = serverUrl + ODATA_ENDPOINT;
    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        datatype: "json",
        url: ODataPath + "(guid'" + accountId + "')",
        beforeSend: function (XMLHttpRequest) {
            XMLHttpRequest.setRequestHeader("Accept", "application/json");
            XMLHttpRequest.setRequestHeader("X-HTTP-Method", "DELETE");
        },
        error: function (xmlHttpRequest, textStatus, errorThrown) {
            alert("Status: " + textStatus + "; ErrorThrown: " + errorThrown);
        }
    });
} 

Reparem que para apagar temos que especificar o header “X-HTTP-Method” como delete.

Nesta solução a grande dificuldade foi mesmo a obtenção do tipo de erros referentes a esta comunicação o resto são coisas que já tínhamos visto anteriormente. Deixo-vos o ficheiro HTML do recurso web que usei, bem como a solução específica para o Dynamics CRM 2015. O meu objetivo é tornar esta solução num género de AddOn tanto para qualquer versão do CRM.

 

Até a próxima.

Tags: , , , , , , ,

Como fazer debug de um plugin e atividade customizada no CRM On-Premise

by Pedro Azevedo 4. December 2014 02:23

Boas pessoal,

Vou continuar a série de fazer debug. Até agora abordamos como realizar debug sobre plugins online e em Web Resources. De referir que a prática que usamos no CRM Online pode-se usar no on-premise também.

Hoje quero falar sobre fazer debug em plugins num ambiente on-premise, que vai permitir realizar um debug em real-time. Para ser mais exato este procedimento é para código servidor, ou seja, tanto dá para plugins como para atividades workflow.

A primeira premissa é termos a nossa DLL do plugin ou da atividade de workflow e o ficheiro PDB (contém informações para realizar debug). Temos que colocar estes dois ficheiros na pasta: “<pasta de instalação>\Microsoft CRM\server\bin\assembly” (C:\Program Files\Microsoft CRM\server\bin\assembly), nesta pasta também devem ser todas as DLLs que se dependa a não ser que estas estejam registadas no GAC.

Uma vez que temos estes ficheiros podemos chegar ao nosso servidor de desenvolvimento e com o projeto do plugin aberto anexarmos um destes processos:

  • w3wp.exe para os plugins síncronos;
  • Microsoft.Crm.Application.Hoster.exe para quando temos uma ligação offline através do Outlook.
  • CrmAsyncService.exe quando se trata de um plugin assíncrono ou uma atividade workflow
  • Microsoft.Crm.Sandbox.WorkerProcess.exe quando temos o plugin registado no modo sandbox.

Vejam a seguir alguns screenshots de como fazer attach a alguns dos processos acima:

Com esta configuração o próximo plugin ou atividade de workflow irá parar no breakpoint definido.

As vezes pode ser frustrante porque o breakpoint não é ativado. Quando isto acontece podemos seguir uma espécie de checklist:

  1. Reset ao IIS (diretamente no IIS ou na linha de comandos escrever iisreset)
  2. Se for uma atividade de workflow ou plugin assíncrono, restaurar o serviço Assync do CRM
  3. Recompilar (em vez de Rebuild, podemos efetuar também o clean) o projeto do plugin ou atividade workflow
  4. Colocar a DLL e o PDB na pasta referida
  5. Registar o assembly pelo Registration Tool
  6. No Visual Studio colocar o breakpoint e anexar os processos referidos.

 

Se no Visual Studio se houver a seguinte mensagem sobre o breakpoint, quer dizer que temos que efetuar a lista acima:

Se o breakpoint apresentar a mensagem a seguir não se preocupem que em principio ele para no breakpoint:

E aqui está um exemplo, neste caso este plugin é lançado aquando da criação de um cliente:

Esta estratégia funciona bem nos ambientes de desenvolvimento, onde temos o visual studio instalado na mesma máquina onde está o servidor de CRM. Caso não tenhamos este cenário necessitamos de um auxiliar que é o Remote Debugger instalado. Podem ver aqui um bom artigo para auxiliar a sua instalação. Depois de estar instalado o Remote Debugger é colocarmos no campo Qualifier o nome do servidor.

 

PS: A versão do CRM usado neste post foi o CRM 2011 com o VS 2010, mas em outros ambientes é praticamente igual.

 

Até a próxima.

Tags: , , ,

CRM 2013 Javascript Object Model - Control

by Pedro Azevedo 30. November 2014 22:04

Boas pessoal,

Hoje na sequência dos posts do object model do Javascript, desta vez vamos ver as opções que temos ao nível do controlo.

getAttribute

function getAttributeTest() {
	Xrm.Utility.alertDialog(Xrm.Page.getControl("subject").getAttribute().getValue());
}

getControlType

Valores possíveis de retorno:

Return Value

Description

standard

A Standard control.

iframe

An IFRAME control

lookup

A Lookup control.

optionset

An OptionSet control

subgrid

A subgrid control

webresource

A web resource control

notes

A Notes control

function getControlTypeTest() {
	Xrm.Utility.alertDialog(Xrm.Page.getControl("subject").getControlType());
}

getDisabled

function getDisabledTest() {
	Xrm.Utility.alertDialog("subject está com disabled a " + Xrm.Page.getControl("subject").getDisabled());
	Xrm.Utility.alertDialog("lastusedincampaign está com disabled a " + Xrm.Page.getControl("lastusedincampaign").getDisabled());
}

setDisabled

function setDisabledTest() {
	Xrm.Page.getControl("subject").setDisabled(true);
} 

getLabel

function getLabelTest() {
	Xrm.Utility.alertDialog(Xrm.Page.getControl("subject").getLabel());
}

setLabel

function setLabelTest() {
	Xrm.Page.getControl("subject").setLabel("Nova Label");
}

getName

function getNameTest() {
	Xrm.Utility.alertDialog(Xrm.Page.getControl("subject").getName());
}

getParent

Dá a secção onde está dentro.

function getParentTest() {
	Xrm.Utility.alertDialog(Xrm.Page.getControl("subject").getParent().getLabel ());
} 

getVisible

function getVisibleTest() {
	Xrm.Utility.alertDialog("O controlo subject está com visible a " + Xrm.Page.getControl("subject").getVisible());
}

setVisible

function setVisibleTest() {
	Xrm.Page.getControl("subject").setVisible(false);
}

setFocus

function setFocusTest() {
	Xrm.Page.getControl("subject").setFocus(true);
}

setNotification

function setNotificationTest() {
	Xrm.Page.getControl("subject").setNotification("Teste a função setNotification");
}

 

clearNotification

function clearNotificationTest() {
	Xrm.Page.getControl("subject").setNotification("Teste a função setNotification", "UMID");
	Xrm.Page.getControl("subject").clearNotification("UMID");
}

Até a próxima

Tags: , , ,

Disponibilização do CRM 2015 - Online e On-premise

by Pedro Azevedo 26. November 2014 01:28

 

Boas pessoal,

Já está disponível o acesso ao CRM 2015, quer na versão on-premise como nas novas instâncias de CRM Online. Para as organizações atuais de CRM Online está previsto para breve que se possa calendarizar o seu upgrade.

Para comprovar este lançamento podem ver na próxima imagem:

Esta foi uma nova instância de CRM Online.

Para as versões on-premise podemos realizar download do servidor aqui e do cliente para o Outlook aqui.

 

Até a próxima.

Tags: , ,

About

Muito bem casado, Pai babado e um gosto muito grande pela tecnologia.

Tenho um lema "Sharing is Learning"

Mais aqui -> http://www.psazevedo.com

Month List