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

Resoluções ano 2015

by Administrator 4. February 2015 06:56

Boas pessoal,

Como se costuma dizer mais vale tarde do que nunca. Mas este início do ano tem sido de grande agitação. São grandes desenvolvimentos a nível profissional e alguns desafios de nível pessoal.

O ano não podia ter começado melhor com a notícia da eleição como MVP do Dynamics CRM e a experiência tem sido fantástica e excedido as expectativas, apesar de não haver nenhum retorno financeiro e até agora sem nenhum benefício ao nível de emprego, toda a informação que passei a ter disponível é algo que não se consegue pagar.

Mas isto tudo só é possível devido aos leitores do blog e a toda a comunidade. A minha participação na comunidade só pode beneficiar com este reconhecimento e vontade não falta para ajudar ainda mais. Se quiserem saber mais sobre esta prémio vejam aqui. E quem quiser ver o meu perfil.

Como resoluções para o ano 2015:

Blog:

Manter ou aumentar a frequência e criar alguns posts em inglês, o primeiro espero que seja brevemente com o lançamento do primeiro add-on criado por mim.

Site:

Espero que até ao Verão escrever o meu primeiro post, já tenho vários na calha só falta mesmo concretizar. Tenho o objetivo de criar pelo menos dois por mês.

Fóruns MSDN:

Continuar a participar e tentar motivar mais pessoas a participarem.

Wiki:

Terminar o trabalho começado.

Certificação:

Algo que não me motiva minimamente mas que tenho que realizar. No final deste ano quero pelo menos fazer as básicas para depois pensar em tornar-me MCP e poder realizar formações.

Your Life Management:

Como ainda não estou a escrever no meu site, vou aqui também definir objetivos que não tem diretamente a ver com o Dynamics CRM, um desses projetos é o YLM que é um portal que estou a realizar. Comecei a fazer este portal para estudar o ASP .NET MVC 5 e outras tecnologias. Vou tentar lançar antes do verão com uma versão simples e quero indo melhorar. Para o final do ano espero começar a migrar para o MVC 6 Sorriso.

 

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

Enviar email ao antigo owner de um registo

by Administrator 23. January 2015 22:47

Boas pessoal,

Mais um desafio do fórum, desta vez, não fui o primeiro a responder. A pergunta era se era possível enviar um email quando a mudança de owner de um registo, através de Workflow (Fluxo de Trabalho) ou Javascript. Bom a primeira resposta sugere que o melhor é recorrer a plugins e confesso que a primeira vez que li concordei com a afirmação, eu até diria que seria a única hipótese.

Velhos vícios, o mal de ter começado com o CRM 4 e com isso que ainda mantenha algumas soluções dessa altura. Como tenho defendido aqui no blog, deve-se sempre optar por uma solução configurável em vez de uma solução programável, isto porque através de configurações “toda” a gente consegue ler, quer seja uma pessoa técnica ou funcional e na altura de upgrades estes objetos costumam ser mais fáceis de migrar.

Pois bem depois de ter lido esta pergunta uma segunda vez, fez-se um click na minha cabeça e porque não realizar isto com os Fluxos de Trabalho? Pois bem com o CRM 2013 foram introduzidos os Fluxos de Trabalho síncronos, que nos possibilitou por exemplo tratar da numeração automática como podem ver neste post. Com este tipo de Fluxo de Trabalho conseguimos aceder ao valor antigo e ao valor novo e com isso enviar um email ao novo\antigo proprietário.

Vamos ver na prática como o fazemos. Primeiro passo é criar um Fluxo de Trabalho síncrono:

image

Reparem que tiramos o pisco (que por defeito está selecionado) que fazia com que o Fluxo de Trabalho corresse assincronamente. Assim o Fluxo de trabalho torna-se síncrono. Avançando com o processo podemos então criar a notificação ao novo e antigo owner. Caso esqueçamos de retirar o pisco não faz mal porque dentro do Fluxo de Trabalho temos a hipótese de transformar o Fluxo de Trabalho em síncrono e vice-versa.

Mas primeiro temos que dizer ao Fluxo de Trabalho que ele pode correr quando houver alteração de um campo, neste caso como se trata do proprietário, podemos escolher quando o registo é atribuído e informamos que ele deve correr antes de guardar o registo e assim apanhar o valor antigo:

image
Pode parecer estranho mas o nosso Fluxo de Trabalho já está configurado, agora basta criar o email e no campo para enviar vamos colocar o proprietário pois configuramos o Fluxo de Trabalho para correr antes de ser assignado outro proprietário:

image

Uma solução que prometia mais trabalho que se tornou simples por utilizarmos a última novidades da plataforma.

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

Sou um Microsoft MVP!!!!!

by Administrator 3. January 2015 17:16

Boas pessoal,

Este ano não poderia ter começado melhor, confesso que não estava a espera, mas fui nomeado Microsoft MVP para o produto Dynamics CRM. Foi com uma grande alegria que recebi esta nomeação que recebo com todo o agrado. E um ótimo incentivo para continuar a colaborar com a comunidade.

Foi engraçado que no primeiro dia do ano não estava nada a espera e fui comemorar o ano novo para a casa dos meus pais. Quando cheguei a casa a noite a primeira coisa foi ver o twitter e comecei a dar os parabéns a muitas pessoas que estão conectadas comigo. Quando vou verificar o email nem acredito que tenho este email na minha caixa de correio:

Posso-vos garantir que ainda estou a tremer. Quero agradecer a toda a comunidade e um abraço especial aos meus amigos Diogo Rito e Thiago Lima que sei que me nomearam diretamente.

 

Já agora BOM ANO para todos vocês.

 

Até a próxima.

Tags:

Participação no evento ISCTE-IUL ACM Student Chapter

by Administrator 20. December 2014 06:49

 

Boas pessoal,

Foi com grande prazer na Quarta Feira (dia 17) participei como orador no evento “ISCTE-IUL ACM Student Chapter” (http://iscte.acm.org/), um evento realizado no ISCTE que tem como objetivo partilhar vários conhecimentos na comunidade e que me deu a oportunidade de apresentar o Microsoft Dynamics CRM 2015.

Os slides da apresentação estão aqui:

E no site do evento algumas fotos minhas:

http://iscte.acm.org/photo-gallery/?wppa-occur=1&wppa-cover=0&wppa-album=7

Mais uma vez o meu objetivo é sempre apresentar esta plataforma para mostrar que não são apenas drag-and-drop de interface e que temos várias opções para estender a plataforma, desde fluxos funcionais, até código Javascript o C#.

Não se esqueçam de ver o site pois todas as quartas-feiras haverá um novo tema.

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

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