martes, 17 de noviembre de 2009

Incluir un link referente a una entidad en emails

En esto de empezar a personalizar el CRM hice un workflow que enviaba un email a un usuario específico cuando alguien creaba una oportunidad, pero claro, el email avisaba simplemente. ¿A qué oportunidad se refería el email? Así que se me ocurrió meter un link en el email que llevara directamente a dicha oportunidad.
Buscando, buceando y trasteando dí con esta solución: (aunque hay alguna más)

PASO 1:
Crear dos atributos en la entidad (Oportunidad x ejemplo) de tipo nvarchar, a uno le llamaremos 'enlace' y a otro 'flag' con una longitud de 250 y 15 respectivamente.
Agregamos estos atributos al formulario de la entidad.

PASO 2:
Abrimos las propiedades del formulario y en el evento ONLOAD escribimos esto:
//Ocultamos los atributos flag y enlace xq no interesan al usuario pero sí a nosotros.
crmForm.all.new_flag_c.style.display='none';
crmForm.all.new_flag_d.style.display='none';
crmForm.all.new_enlace_c.style.display='none';
crmForm.all.new_enlace_d.style.display='none';

var url ="<a href='http://crm-dynamic/CRM/sfa/leads/edit.aspx?id="

if(crmForm.FormType == 2)
{
 //Si es el formulario de modificación 
 if ((crmForm.all.new_flag.DataValue == null )||(crmForm.all.new_flag.DataValue==""))
  crmForm.all.new_enlace.DataValue= url + crmForm.ObjectId + "'> clic </a>";
 else
 {
  //Comprobamos el Flag para saber en que estado nos encontramos
  switch(crmForm.all.new_flag.DataValue)
  {
   case "SAVEANDCLOSE":
    crmForm.all.new_flag.DataValue = "HECHO"
    crmForm.all.new_enlace.DataValue= url + crmForm.ObjectId + "'>aqui.</a>";
    crmForm.SaveAndClose();
   break
   case "SAVE":
    crmForm.all.new_flag.DataValue = "HECHO"
    crmForm.all.new_enlace.DataValue= url + crmForm.ObjectId + "'>aqui.</a>";
    crmForm.Save();
   break
   case "SAVEANDNEW":
    crmForm.all.new_flag.DataValue = "HECHO"
    crmForm.all.new_enlace.DataValue= url + crmForm.ObjectId + "'>aqui.</a>";
    crmForm.SubmitCrmForm(59, true, true, false);
   break
   default: 
   break
  }
 }
}


PASO 3:
en el evento ONSAVE escribimos esto:
//Si el Flag ya está relleno no haremos nada.
if ((crmForm.all.new_flag.DataValue == null )||(crmForm.all.new_flag.DataValue==""))
{
if (event.Mode==2)
crmForm.all.new_flag.DataValue="SAVEANDCLOSE";
else if (event.Mode==59)
crmForm.all.new_flag.DataValue="SAVEANDNEW";
else
crmForm.all.new_flag.DataValue="SAVE";
//Forzamos a guardar para que se cree el GUID del registro.
crmForm.Save();          

//Impedimos que haga el guardado del evento original puesto que lo hemos manipulado
event.returnValue=false;
return false;
}


PASO 4:
Editamos la tarea 'Enviar email' del workflow.
Ahora podemos agregar el atributo 'enlace' de la entidad quedando de la siguiente forma:
Se ha creado una nueva Oportunidad con el tema {Tema(Oportunidad)} y requiere su validación.
Puede visualizarla haciendo clic {enlace(Oportunidad)}


CONSIDERACIONES:
La url que guardamos en el atributo 'enlace' en el ejemplo es
http://crm-dynamic/CRM/sfa/leads/edit.aspx?id= donde crm-dynamic es el nombre de nuestro servidor CRM y CRM es el nombre de la compañía matriz (es case sensitive). El resto de la url dependerá de la entidad que vayamos a manejar, copio aquí los posibles casos:
  • SFA - Sales Force Automation (accounts,contacts etc)
  • CS - Customer Service (cases,contracts etc)
  • MA - Marketing Automation (campaigns, marketing lists etc)

Sales Force Automation:
  Accounts: http://<crm-url>/sfa/accts/edit.aspx
  Contacts: http://<crm-url>/sfa/conts/edit.aspx
  Leads: http://<crm-url>/sfa/leads/edit.aspx
  Opportunities: http://<crm-url>/sfa/opps/edit.aspx

Marketing Automation
  Campaigns: http://<crm-url>/ma/camps/edit.aspx
  Marketing Lists: http://<crm-url>/ma/lists/edit.aspx
  Campaign Response: http://<crm-url>/ma/campaignresponse/edit.aspx

Case Management:
  Cases: http://<crm-url>/cs/cases/edit.aspx
  Contracts: http://<crm-url>/cs/contracts/edit.aspx
  KB Articles: http://<crm-url>cs/articles/edit.aspx

Custom Entities:
  Custom: http://<crm-url>/userdefined/edit.aspx

martes, 29 de septiembre de 2009

Pasando variables entre páginas aspx

No me andaré con rodeos...

Esto en la pagina q llama
Context.Items("usr") = "1001" 
Context.Items("nam") = "Rene" 
Context.Items("Memo") = " Aki va el texto "
Server.Transfer("WebForm2.aspx") <- pagina a la q se llama 
y en la q se llama lee asi los valores:
txtusr.Text = Context.Items("usr") 
txtNam.Text = Context.Items("nam") 
txtMemo.Text = Context.Items("Memo") 

miércoles, 23 de septiembre de 2009

Cómo ocultar pestañas (tabs) en los formularios del CRM 4.0

Una vez más hay que recurrir a Javascript. En el evento OnLoad() del formulario hay que poner el siguiente código:

if (UserHasRole("Administrador")) {   
   crmForm.all.tab1Tab.style.display = 'inline';
}
else {  
   crmForm.all.tab1Tab.style.display = 'none';
}

Fijaros en tab1Tab donde el número indica el ordinal de la pestaña que queremos ocultar, como los arrays, empieza por cero. He reutilizado la función UserHasRole() de la que ya hablé en el post Cómo habilitar un campo en función del rol del usuario.

Fuente: Todo Microsoft CRM

jueves, 17 de septiembre de 2009

Cómo habilitar un campo en función del rol del usuario

Abrimos las propiedades del formulario.
Seleccionamos el evento OnLoad y pulsamos en el botón Editar.
Acuérdate de marcar el check "El evento está habilitado" porque si no, no funciona el codigo javascript que metas.

El código que tienes que meter dentro de function OnLoad() es:
if (UserHasRole("Director Ventas") || UserHasRole("Director Compras")) {    
   document.forms[0].all.new_validacioncomercial_d.disabled=false;
} 
else {   
   document.forms[0].all.new_validacioncomercial_d.disabled=true;
}


/* *************************************************** */


function UserHasRole(roleName)  
{  
   //oXml es un objeto que llama a obtener el rol  
   var oXml = GetCurrentUserRoles();  
   if(oXml != null)  
   {  
      //Selecciono el nodo Texto  
      var roles = oXml.selectNodes("//BusinessEntity/q1:name");  
      if(roles != null)  
      {  
         for( i = 0; i < roles.length; i++)  
         {      
            if(roles[i].text == roleName)  
            {  
               //Devuelve true si el rol es el indicado  
               return true; 
            }  
         }   
      }  
   }  
   //Sino devuelve falso  
   return false;  
}  


function GetCurrentUserRoles()  
{
   // Compongo el xml 
   var xml = "" +  
   "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +  
   "<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\">" +  
   GenerateAuthenticationHeader() +  
   " <soap:Body>" +  
   " <RetrieveMultiple xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">" +  
   " <query xmlns:q1=\"http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:QueryExpression\">" +  
   " <q1:EntityName>role</q1:EntityName>" +  
   " <q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +  
   " <q1:Attributes>" +  
   " <q1:Attribute>name</q1:Attribute>" +  
   " </q1:Attributes>" +  
   " </q1:ColumnSet>" +  
   " <q1:Distinct>false</q1:Distinct>" +  
   " <q1:LinkEntities>" +  
   " <q1:LinkEntity>" +  
   " <q1:LinkFromAttributeName>roleid</q1:LinkFromAttributeName>" +  
   " <q1:LinkFromEntityName>role</q1:LinkFromEntityName>" +  
   " <q1:LinkToEntityName>systemuserroles</q1:LinkToEntityName>" +  
   " <q1:LinkToAttributeName>roleid</q1:LinkToAttributeName>" +  
   " <q1:JoinOperator>Inner</q1:JoinOperator>" +  
   " <q1:LinkEntities>" +  
   " <q1:LinkEntity>" +  
   " <q1:LinkFromAttributeName>systemuserid</q1:LinkFromAttributeName>" +  
   " <q1:LinkFromEntityName>systemuserroles</q1:LinkFromEntityName>" +   " <q1:LinkToEntityName>systemuser</q1:LinkToEntityName>" +  
   " <q1:LinkToAttributeName>systemuserid</q1:LinkToAttributeName>" +    " <q1:JoinOperator>Inner</q1:JoinOperator>" +  
   " <q1:LinkCriteria>" +  
   " <q1:FilterOperator>And</q1:FilterOperator>" +  
   " <q1:Conditions>" +  
   " <q1:Condition>" +  
   " <q1:AttributeName>systemuserid</q1:AttributeName>" +  
   " <q1:Operator>EqualUserId</q1:Operator>" +  
   " </q1:Condition>" +  
   " </q1:Conditions>" +  
   " </q1:LinkCriteria>" +  
   " </q1:LinkEntity>" +  
   " </q1:LinkEntities>" +  
   " </q1:LinkEntity>" +  
   " </q1:LinkEntities>" +  
   " </query>" +  
   " </RetrieveMultiple>" +  
   " </soap:Body>" +  
   "</soap:Envelope>" +  
   "";  
   var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");  
   xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);  
   xmlHttpRequest.setRequestHeader("SOAPAction"," http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");  
   xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");  
   xmlHttpRequest.setRequestHeader("Content-Length", xml.length);  
   xmlHttpRequest.send(xml);  
   var resultXml = xmlHttpRequest.responseXML;  
   return(resultXml);  
}

Controlar cuando cierra el usuario el navegador

Controlar cuando el usuario nos cierra el navegador ¿Es posible?
Pues sí, es posible, empleando un pequeño truquillo en la maquetación en nuestro Site.

En el cliente en el que me encuentro actualmente me lo pidieron como requisito indispensable, puesto que intentan asemejar el comportamiento de sus aplicaciones Winform y ademas así liberar todos los objetos asociados a esa sesión. Otro requerimiento es que un usuario sólo pueda abrir un ventana de IExplorer para cada aplicación, pero esto lo veremos en la siguiente entrega.

Como comenté en el primer párrafo todo se basa en el modélo de maquetación del site. Es necesario tener una página que nunca se descargue y que solo se dispare su evento JavaScript OnUnload bien cuando nos cierre el navegador de la dichosa X o cuando pulse nuestro botón de desconexión, por lo tanto vamos a utilizar una página principal que sea contenedora de otras páginas mediante un IFRAME:
<div id="content">
<iframe id="contenido" src="contenido1.aspx" frameBorder="0" width="100%" height="400">
</div>
En el IFRAME se realizará la navegación de nuestro Site y así podremos controlar cuando nos cierra el navegador ¿Pero como? pues sencillo, lo controlaremos con un campo oculto:
<input id="logout" type="hidden" value="0">
al que asignaremos un valor distinto de 0 desde nuestro botón de desconexión desde el Code-Behind:
private void imgBtnSalir_Click(object sender, System.Web.UI.ImageClickEventArgs e) 
{
   FormsAuthentication.SignOut();
   Session.Abandon();
   Page.RegisterStartupScript("logout", "<script>document.getElementById(\"logout\").value=1;</script>");
   Page.RegisterStartupScript("close","<script>window.close();</script>");
}
Y en el evento JavaScript OnUnload comprobamos ese valor con la función Logout (<body OnUnload="Logout();">):
function Logout() 
{
   var logout = document.getElementById("logout");
   if (logout.value == 0)
   {
      openCenterPopUp("logout.aspx", "", 600, 70);
   }
} 

Si es 0, abrimos la ventana logout.aspx que se encarga de liberar los recursos de la sesión y de hacer un logout de la autenticación (Aunque yo esto no lo hago en mi cliente puesto que es una intranet y utilizo la autenticación de Windows integrada pero para el ejemplo he preferido añadirlo):

private void Page_Load(object sender, System.EventArgs e)
{
   FormsAuthentication.SignOut();
   Session.Abandon();
   this.RegisterStartupScript("close", "<script>setTimeout(\"window.close();\",10000);</script>");
}

Fuente: esto lo saqué del blog "Amigo mío Siempre estas Programando en .NET"

Abrir distintas versiones de proyectos Asp.Net

Si tienes que abrir un proyecto creado con Visual Studio (VS) 2002 con VS2003 simplemente hay que editar con el bloc de notas el fichero .vbproj y cambiar los siguientes datos:
En el caso de los proyectos creados con VS2002 guarda esta información:
ProductVersion = "7.0.9466"
SchemaVersion = "1.0"

Mientras que la versión 2003, guardará esta información:
ProductVersion = "7.10.2292"
SchemaVersion = "2.0"

Cambia uno por otro y listo!
Para versiones posteriores, Visual Studio ya dispone de un conversor automático.

CRM: Empezando...

Este post es para todo aquel que se va a enfrentar a la personalización del CRM de su empresa.
Para empezar ¿qué es un CRM? las siglas son de "Customer Relationship Management" ¿lo cualo? sí tranquil@, no es ni más ni menos que una aplicación web de Microsoft orientada a la gestión de todo lo que tenga que ver con el cliente en tu empresa. Es decir, gestión de contactos, cuentas, clientes potenciales, ofertas, pedidos, campañas de marketing, catálogo de productos, etc... todo ello "salvaguardado" por un sistema de seguridad basado en roles de usuario.

¿Cómo meterle mano a esto?
El CRM de Microsoft se basa en "entidades", es algo parecido a los objetos en programación. Una entidad "Contacto" tiene una serie de atributos como "Nombre", "Teléfono", etc... además puede estar relacionada con otras entidades 1:N, N:1 o N:N. En el apartado Personalización del módulo Configuración tenéis el listado de todas ellas. Se pueden crear nuevas entidades.

¿Y cómo programo algo para el CRM?
Pues hay varias opciones, incluyendo JavaScript en los eventos de los formularios, desarrollando en Asp.Net un aspx y agregar un iFrame al formulario de la entidad cuyo contenido será este aspx, desarrollando un Plugin o hacer llamadas a WebServices.

Poco a poco iré publicando las utilidades que desarrolle o que haya encontrado por ahí.

INTRODUCCION

Este blog pretende ser un compendio de trozos de código que puedan echar una mano a otros desarrollador@s. Las tecnologías y lenguajes serán variados, pero casi siempre centrándose en productos Microsoft.
Muchos post serán una especie de corta/pega de otros, se pondrán las referencias originales a esos post por si queréis más información.