sexta-feira, 19 de dezembro de 2008

>> CRM 4.0 - AJAX + Preenchimento Automático de Lookup

Já tiveram a necessidade de preenchimento automático de um campo Lookup, baseado na escolha de alguma informação em outro campo? Somente em JavaScript?

Segue código template que ilustra isto, de forma que, ao selecionar uma Conta na Oportunidade, o sistema atualize a Lista de Preço da Oportunidade (existente na Conta).

Note que o código (JavaScript) utiliza o Web Services do CRM, método Retrieve para busca de dados de uma entidade, baseado no ID.

No [OnChange] do campo [Cliente Provável] da Oportunidade, adicionar o template abaixo:

P.S.: Substitua o nome [CAMPO_RETORNO] pelo nome do campo que representa o ID da Lista de Preço da Conta.

---- ONCHANGE FIELD ACTION ----
getAccountPriceLevel();

function getAccountPriceLevel()
{
var server = window.location.host;
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
xmlhttp.open("POST", "http://" + server + "/mscrmservices/2007/crmservice.asmx", true);
xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Retrieve");

var soapBody = "<soap:Body><Retrieve xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\"><entityName>account</entityName>";
soapBody += "<id>" + crmForm.all.customerid.DataValue[0].id + "</id>";
soapBody += "<columnSet xmlns:q1=\"http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:ColumnSet\"><q1:Attributes>";
soapBody += "<q1:Attribute>CAMPO_RETORNO</q1:Attribute>";
soapBody += "</q1:Attributes></columnSet>";
soapBody += "</Retrieve></soap:Body>";

var soapXml = "<soap:Envelope " +
"xmlns: soap='http://schemas.xmlsoap.org/soap/envelope/' "+
"xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' "+
"xmlns:xsd='http://www.w3.org/2001/XMLSchema'>";
soapXml += GenerateAuthenticationHeader();
soapXml += soapBody;
soapXml += "</soap:Envelope>";

xmlhttp.onreadystatechange = StateChangeAccountPriceLevel;
xmlhttp.send(soapXml);
}


function StateChangeAccountPriceLevel()
{
if (xmlhttp.readyState == 4)
{
var oNodes = xmlhttp.responseXML.selectSingleNode("//RetrieveResult").childNodes;
var priceLevelID = oNodes[0].text;

if (crmForm.all.pricelevelid.DataValue == null)
{
var lookupData = new Array();
var lookupItem= new Object();
lookupItem.id = priceLevelID;
lookupItem.typename = 'pricelevel';
lookupItem.name = 'Lista de Preço Padrão';
lookupData[0] = lookupItem;
crmForm.all.pricelevelid.DataValue = lookupData;
}
}
}

quinta-feira, 18 de dezembro de 2008

>> CRM 4.0 - Data Migration Manager

[Data Migration Manager] - desenvolvido para transporte de dados em massa de uma fonte de dados para a base do CRM.

Segue algumas orientações para uso do DMM:

1 - Demostração

Veja este "webcast" que ensina o uso do DMM - http://channel9.msdn.com/posts/jodonnell/Microsoft-Dynamics-CRM-40--Data-Migration-Manager-with-John-ODonnell/

2 - Restrições

- NÃO instale o DMM no Servidor do CRM (DMM só roda no [Windows Vista] ou [Windows XP]);
- Alguns desenvs instalam o DMM no Servidor do [SQL SERVER - 32 bits];

- Se o banco de dados for de [64 bits] o DMM Não vai funcionar;

- Use uma máquina Client, 32 bits com [local SQL Express] para o DataBase de Migração;
- Instale o DMM escolhendo a opção do banco de dados [SQL SERVER 2005 EXPRESS EDITION];

- Instale e rode o DMM com um usuário [local admin] e [CRM Admin]. Isto é importante porque o DMM usa o Web Services do CRM! Inclusive o DMM só pode ser usado pelo usuário que o instalou.

- DMM não pode ser usado enquanto o [Microsoft Office Outlook] estiver rodando;

Fonte adicional: http://rc.crm.dynamics.com/rc/regcont/en_us/Live/help/How_dmw_install.htm

terça-feira, 16 de dezembro de 2008

>> CRM 4.0 - "Limitações" no Acompanhamento de Atividades

Apesar da flexibilidade no uso de Acompanhamento de Atividades no CRM (http://gtezini.blogspot.com/2008/12/crm-40-boto-acompanhamento-de.html) existem algumas "limitações" que impedem o uso deste recurso.

Por exemplo, se você tentar efetuar um Acompanhamento (do tipo Tarefa) para uma atividade do tipo [Telefonema], atividade esta pertencente a uma [Campanha Rápida], ao salvar o acompanhamento, receberá a seguinte mensagem do CRM:

"[Campanhas rápidas] não podem ter [tarefa] como uma [atividade de acompanhamento]"

O que isto significa?

Seguindo o mesmo raciocínio do [KB 909824] que explica a impossibilidade de criação de [acompanhamento de atividade do tipo tarefa] para uma [atividade do tipo email]...

Podemos explicar este caso da seguinte forma:

"Quando você tenta criar um [acompanhamento do tipo tarefa], o Microsoft CRM configura o campo "Regarding" da [atividade de acompanhamento do tipo tarefa]. Para este campo, o Microsoft CRM tenta usar o valor do campo "Regarding" da [atividade de telefonema]. Contudo, não existe um relacionamento entre o [acompanhamento do tipo tarefa] e a [campanha rápida]."

Por este motivo aparece a msg de restrição.

Solução MS:

Use os seguintes tipos de atividades para acompanhamento:

- Telefonema;
- Carta;
- Fax;
- E-mail;
- Apontamento.

>> CRM 4.0 - Botão Acompanhamento de Atividades

Dica rápida...

Para que serve o botão de "Acompanhamento" existente nas telas de detalhes dos vários tipos de atividades do CRM?

O Acompanhamento (ou FollowUp) também é uma atividade no CRM, dos tipos tarefa, email, telefonema, etc. É um recurso que faz parte do Painel "Assistente de Formulário" do CRM.

Por exemplo, você pode criar uma atividade de serviço de acompanhamento futuro. Imagine uma empresa que acabou de concluir a troca de óleo para um cliente; você pode criar uma nova atividade de serviço para outra troca de óleo em seis meses, de forma a "associar" uma tarefa em outra.

Veja este exemplo: http://rc.crm.dynamics.com/rc/regcont/pt_br/op/articles/followup.aspx

sexta-feira, 12 de dezembro de 2008

>> CRM 4.0 - Revisar Quota Programaticamente

Existem alguns desenvolvimentos para o Dynamics CRM (via SDK) que poderíamos categorizar como sendo "não tão óbvios". Por exemplo, não existem exemplos completos para, programaticamente, Revisar uma Quota.

Juntando o "quebra-cabeça" para este caso, segue abaixo função Template.

public string ReviseQuote(string id)
{
// Set up the CRM Service.
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = 0;
token.OrganizationName = "OrgName";

CrmService CrmWebService = new CrmService();
CrmWebService.Url = "http://localhost:5555/mscrmservices/2007/crmservice.asmx";
CrmWebService.CrmAuthenticationTokenValue = token;
CrmWebService.Credentials = System.Net.CredentialCache.DefaultCredentials;

string idRet = string.Empty;
Boolean CloseQuoteRequestOk = true;

// 1 - CLOSE QUOTE ACTIVITY

crmSdk.BusinessEntity beQuote = CrmWebService.Retrieve(crmSdk.EntityName.quote.ToString(), new Guid(id), new crmSdk.AllColumns());
crmSdk.quote _quote = beQuote as crmSdk.quote;

if (_quote == null) return string.Empty;

quoteclose _quoteClose = new quoteclose();
Lookup _lookUp = new Lookup();
_lookUp.type = EntityName.quote.ToString();
_lookUp.Value = new Guid(id);
CrmDateTime _crmDate1 = new CrmDateTime();
CrmDateTime _crmDate2 = new CrmDateTime();
_crmDate1.Value = new DateTime(1999, 12, 31, 23, 0, 0).ToString("u");
_crmDate2.Value = DateTime.Now.ToString("s");
_quoteClose.quoteid = _lookUp;
_quoteClose.subject = "Oferta Fechada (Revisada) " + _quote.quotenumber;
_quoteClose.quotenumber = _quote.quotenumber;
_quoteClose.revision = _quote.revisionnumber;
_quoteClose.actualstart = _crmDate1;
_quoteClose.actualend = _crmDate2;
CloseQuoteRequest closeQuoteRequest = new CloseQuoteRequest();
closeQuoteRequest.QuoteClose = _quoteClose;
closeQuoteRequest.Status = 7; // Revised
try
{
CrmWebService.Execute(closeQuoteRequest);
}
catch (System.Web.Services.Protocols.SoapException ex)
{
CloseQuoteRequestOk = false;
System.Diagnostics.EventLog.WriteEntry("Application", "CloseQuoteRequest ERROR => QuoteID: [" + id + "] / Message: " + ex.Detail.InnerText);
}

if (!CloseQuoteRequestOk) return string.Empty;

// 2 - REVISE QUOTE
ReviseQuoteRequest reviseReq = new ReviseQuoteRequest();
ColumnSet columns = new ColumnSet();
columns.Attributes = new string[] { "quoteid" };
reviseReq.ColumnSet = columns;
reviseReq.QuoteId = new Guid(id);
reviseReq.ReturnDynamicEntities = false;
ReviseQuoteResponse reviseResp = null;
try
{
reviseResp = (ReviseQuoteResponse)CrmWebService.Execute(reviseReq);
idRet = "{" + (reviseResp.BusinessEntity as quote).quoteid.Value.ToString().ToUpper() + "}";
}
catch (System.Web.Services.Protocols.SoapException ex)
{
idRet = string.Empty;
System.Diagnostics.EventLog.WriteEntry("Application", "ReviseQuoteRequest ERROR => QuoteID: [" + id + "] / Message: " + ex.Detail.InnerText);
}
return idRet;
}

quarta-feira, 10 de dezembro de 2008

>> CRM 4.0 - Envio de Email Template (com anexos)

Segue código completo para envio de Email Template, com anexos.

A função SendEmailTemplate recebe como parâmetros o número, nome e ID da entidade como referência para o email (campo "referente a"), a descrição do Template do CRM a ser usado para envio do email, para quais usuários enviar e os anexos.


public static string SendEmailTemplate(int otc, string CrmEntityName, Guid oID, string templateTitle, string[] aUser, string[] aFiles)
{
// Set up the CRM Service.
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = 0;
token.OrganizationName = "OrgName";

CrmService CrmWebService = new CrmService();
CrmWebService.Url = "http://localhost:5555/mscrmservices/2007/crmservice.asmx";
CrmWebService.CrmAuthenticationTokenValue = token;
CrmWebService.Credentials = System.Net.CredentialCache.DefaultCredentials;

Guid emailTemplateID = getTemplate(CrmWebService, otc, templateTitle);

if (emailTemplateID.Equals(Guid.Empty))
{
string err = "Não foi possível localizar o Email template ";
err += "com a palavra chave [" + templateTitle + "] para a entidade [" + otc.ToString() + "].";
return err;
}

crmSdk.WhoAmIRequest userRequest = new crmSdk.WhoAmIRequest();
crmSdk.WhoAmIResponse userResponse = (crmSdk.WhoAmIResponse)CrmWebService.Execute(userRequest);

crmSdk.BusinessEntity beSystemUser = CrmWebService.Retrieve(crmSdk.EntityName.systemuser.ToString(), userResponse.UserId, new crmSdk.AllColumns());
crmSdk.systemuser _systemuser = beSystemUser as crmSdk.systemuser;

if (_systemuser.internalemailaddress == null)
{
string err = "Seu usuário, no CRM, não possui EMAIL cadastrado (Email Primário).";
return err;
}

crmSdk.Owner _owner = new crmSdk.Owner();
_owner.type = crmSdk.EntityName.systemuser.ToString();
_owner.Value = userResponse.UserId;

// Instantiate the Template as an EMAIL Object.
crmSdk.InstantiateTemplateRequest instTemplate = new crmSdk.InstantiateTemplateRequest();
instTemplate.TemplateId = emailTemplateID;
instTemplate.ObjectId = oID;
instTemplate.ObjectType = CrmEntityName;
crmSdk.InstantiateTemplateResponse instTemplateResponse = null;

try
{
instTemplateResponse = (crmSdk.InstantiateTemplateResponse)CrmWebService.Execute(instTemplate);
}
catch (System.Web.Services.Protocols.SoapException ex)
{
return "InstantiateTemplateResponse Error - " + ex.Detail.InnerText;
}
catch (Exception ex)
{
return "InstantiateTemplateResponse Error - " + ex.Message;
}

//Convert the returned entity to an email entity, this is really just the merged template body
crmSdk.email emailFromTemplate = (crmSdk.email)instTemplateResponse.BusinessEntityCollection.BusinessEntities[0];

//Add Recipients to the email
CrmUtil.crmSdk.activityparty partyFROM = new CrmUtil.crmSdk.activityparty();
partyFROM.partyid = new CrmUtil.crmSdk.Lookup();
partyFROM.partyid.type = CrmUtil.crmSdk.EntityName.systemuser.ToString();
partyFROM.partyid.Value = userResponse.UserId;

crmSdk.activityparty[] aParties = new crmSdk.activityparty[aUser.Length];

int loop = 0;

for (int j = 0; j < aUser.Length; j++)
{
aParties[loop] = new crmSdk.activityparty();
aParties[loop].partyid = new crmSdk.Lookup();
aParties[loop].partyid.type = crmSdk.EntityName.systemuser.ToString();
aParties[loop].partyid.Value = new Guid(aUser[j]);
loop++;
}

emailFromTemplate.from = new crmSdk.activityparty[] { partyFROM };
emailFromTemplate.to = aParties;
emailFromTemplate.ownerid = _owner;
emailFromTemplate.regardingobjectid = new crmSdk.Lookup();
emailFromTemplate.regardingobjectid.type = CrmEntityName;
emailFromTemplate.regardingobjectid.Value = oID;

// Create the Email
crmSdk.TargetCreateEmail targetCreate = new crmSdk.TargetCreateEmail();
targetCreate.Email = emailFromTemplate;
crmSdk.CreateRequest request = new crmSdk.CreateRequest();
request.Target = targetCreate;
crmSdk.CreateResponse response = null;
try
{
response = (crmSdk.CreateResponse)CrmWebService.Execute(request);
}
catch (System.Web.Services.Protocols.SoapException ex)
{
return "TargetCreateEmail Error - " + ex.Detail.InnerText;
}

// Add Attachments
string msgRet = AddAttachments(response.id, aFiles);
if (!string.IsNullOrEmpty(msgRet)) return msgRet;

// Send Email
crmSdk.SendEmailRequest req = new crmSdk.SendEmailRequest();
req.EmailId = response.id;
req.TrackingToken = "";
req.IssueSend = true;
crmSdk.SendEmailResponse res = null;
try
{
res = (crmSdk.SendEmailResponse)CrmWebService.Execute(req);
}
catch (System.Web.Services.Protocols.SoapException ex)
{
return "SendEmailRequest - " + ex.Detail.InnerText;
}

return "Email criado com sucesso.";
}

private static Guid getTemplate(crmSdk.CrmService CrmWebService, int otc, string templateTitle)
{
crmSdk.QueryExpression _Query = new crmSdk.QueryExpression();
_Query.EntityName = crmSdk.EntityName.template.ToString();
_Query.ColumnSet = new crmSdk.AllColumns();

crmSdk.ConditionExpression _ce1 = new crmSdk.ConditionExpression();
crmSdk.ConditionExpression _ce2 = new crmSdk.ConditionExpression();

_ce1.AttributeName = "templatetypecode";
_ce1.Operator = crmSdk.ConditionOperator.Equal;
_ce1.Values = new object[] { otc };

_ce2.AttributeName = "title";
_ce2.Operator = crmSdk.ConditionOperator.Like;
_ce2.Values = new object[] { "%" + templateTitle + "%" };

crmSdk.FilterExpression _FilterExpression = new crmSdk.FilterExpression();
_FilterExpression.FilterOperator = crmSdk.LogicalOperator.And;
_FilterExpression.Conditions = new crmSdk.ConditionExpression[] { _ce1, _ce2 };

_Query.Criteria = _FilterExpression;
crmSdk.BusinessEntityCollection _Bec = CrmWebService.RetrieveMultiple(_Query);

Guid gResult = Guid.Empty;

if (_Bec.BusinessEntities.Length > 0)
{
crmSdk.template oTemplate = _Bec.BusinessEntities[0] as crmSdk.template;
gResult = oTemplate.templateid.Value;
}

return gResult;
}

private static string AddAttachments(crmSdk.CrmService CrmWebService, Guid emailID, string[] aFiles)
{
string retMsg = string.Empty;
string sServerPath = HttpContext.Current.Server.MapPath(@"\");
sServerPath = sServerPath.Replace(@"\", "||");

for (int i = 0; i < aFiles.Length; i++)
{
string fileNameCRM = aFiles[i];
RemoveSpecialChars(ref fileNameCRM);

string file = sServerPath + @"UploadFiles\\" + aFiles[i];
string Newfile = sServerPath + @"UploadFiles\\" + fileNameCRM;

file = file.Replace("||", "\\\\");
Newfile = Newfile.Replace("||", "\\\\");

string additionalError = "\nTentando adicionar anexo: " + Newfile;

FileInfo info = new FileInfo(file);
string sExtension = info.Extension;

try
{
string fileCompare1 = aFiles[i].Trim().ToLower();
string fileCompare2 = fileNameCRM.Trim().ToLower();
if (!fileCompare1.Equals(fileCompare2))
{
info.MoveTo(Newfile);
}
}
catch (IOException ex)
{
retMsg ="IOException (MoveTo) - " + ex.Message + additionalError;
return retMsg;
}
catch (Exception ex)
{
retMsg = "IOException (MoveTo) - " + ex.Message + additionalError;
return retMsg;
}

FileStream fl = File.OpenRead(Newfile);

byte[] byteData = new byte[fl.Length];
fl.Read(byteData, 0, (int)fl.Length);
fl.Close();

crmSdk.activitymimeattachment oMimeAttach = new crmSdk.activitymimeattachment();
oMimeAttach.activityid = new crmSdk.Lookup();
oMimeAttach.activityid.Value = emailID;
oMimeAttach.activityid.type = crmSdk.EntityName.email.ToString();
oMimeAttach.attachmentnumber = new crmSdk.CrmNumber();
oMimeAttach.attachmentnumber.Value = i;
oMimeAttach.filename = fileNameCRM;
oMimeAttach.body = Convert.ToBase64String(byteData, 0, byteData.Length);

try
{
CrmWebService.Create(oMimeAttach);
try { info.Delete(); }catch { }
}
catch (SoapException ex)
{
retMsg = "Activitymimeattachment Error - " + ex.Detail.InnerText + additionalError;
}
catch (Exception ex)
{
retMsg = "Activitymimeattachment Error - " + ex.Message + additionalError;
}

}
return retMsg;
}

private static string RemoveSpecialChars(ref string fileName)
{
fileName = Regex.Replace(fileName, @"[^\w\.@-]", "");
fileName = fileName.Replace("ª", "");
fileName = fileName.Replace("º", "");
return fileName;
}

>> CRM 4.0 - Converter System.DateTime para CRMDateTime

public static CrmDateTime ConvertToCRMDateTime(DateTime dateTime)
{
CrmDateTime crmDateTime = new CrmDateTime();
crmDateTime.date = dateTime.ToShortDateString();
crmDateTime.time = dateTime.ToShortTimeString();
TimeSpan offset = TimeZone.CurrentTimeZone.GetUtcOffset(dateTime);
string sOffset = string.Empty;
if (offset.Hours < 0)
{
sOffset = "-" + (offset.Hours * -1).ToString().PadLeft(2, '0');
}
else
{
sOffset = "+" + offset.Hours.ToString().PadLeft(2, '0');
}

sOffset += ":" + offset.Minutes.ToString().PadLeft(2, '0');
crmDateTime.Value = dateTime.ToString(string.Format("yyyy-MM-ddTHH:mm:ss{0}", sOffset));

return crmDateTime;
}