17/12/08

Utilidades para e-mail en Workflows

Un requisito muy habitual - y que falta en CRM 4.0 - es la posibilidad de añadir enlaces en mensajes de correo creados por Workflows. El problema es que el identificador GUID del registro no está disponible en el editor de campos dinámicos del Workflow. Lo que han hecho en el blog del equipo de desarrollo de CRM es desarrollar una actividad personalizada de Workflow que fabrica un campo de texto ya formateado como hiperenlace a partir del identificador de un registro. O sea:

<a href="{0}">{0}</a>

El artículo completo aquí:

http://blogs.msdn.com/crm/archive/2008/12/15/workflow-e-mail-utilities.aspx

Además trata otro problema que - la verdad - no me había encontrado: cuando se copia un campo de tipo ntext (memo) en un correo, los saltos de línea desaparecen.

12/12/08

Fallan las credenciales al acceder a Reporting Services

Este post es uno de esos para no olvidar cómo c*** arreglé aquel error. Se trata de lo siguiente: estás instalando CRM en una máquina y tienes la base de datos en otra. Desde la máquina local de SQL Server el Report Server y el Report Manager se ven sin problema ninguno. Ahora, cuando intentas acceder desde la máquina de CRM, empieza a pedirte credenciales (que no debería, está configurado para autenticación integrada con AD) y finalmente te lanza un error 401. Bueno, pues la solución la proporcionó este mensaje de un tal Janos. Se abre una consola y desde C:\Inetpub\AdminScripts se escribe:


cscript adsutil.vbs get w3svc/NTAuthenticationProviders



Si devuelve el mensaje The parameter "NTAuthenticationProviders" is not set at this node es que vamos bien. Escribimos lo siguiente:


cscript adsutil.vbs set w3svc/NTAuthenticationProviders "NTLM"



Y asunto arreglado. Me da vergüenza reconocerlo pero ... no sé muy bien cuál era el problema. Aparentemente, algo está mal en la metabase del IIS.

2/12/08

Aceleradores de CRM: eService

Aceleradores de CRM Desde el viernes tenemos un nuevo vecino en el programa de aceleradores de CRM, el eService accelerator. Se trata de un punto de partida - no una solución completa - para crear un portal de autoservicio para clientes. Desde el portal podrán manejar su perfil, crear nuevos casos, programar servicios o consultar la base de conocimiento, todo con una interfaz web sencilla y sin necesidad de ser usuario de CRM. Esto último tiene implicaciones de licencia, ya que nos lleva al tenebroso terreno del licenciamiento CRM External Connector. Por cierto, ¿existe este licenciamiento para la versión 4.0?

Se puede descargar aquí junto con un video explicativo para la instalación.

27/11/08

Form Reporter: Generador de documentación para CRM

Esta nueva herramienta presentada por Jim Daly en este post del blog del equipo de desarrollo de CRM puede ser de mucha utilidad para los fans de la documentación de proyectos. El Microsoft CRM 4.0 Form Reporter es una aplicación que permite crear un documento Word 2007 con información de entidades, atributos, elementos de interfaz de usuario etc. Todo (o casi todo) lo que contiene un fichero customizations.xml exportado desde CRM. formreporter

Seguro que ahorrará mucho tiempo para documentar nuestros desarrollos o para - quizás más importante - entender los de terceros. Se ha desarrollado usando transformaciones XSL y la SDK para desarrollo de Open XML , así que requiere .NET Framework 3.5 y Word 2007.

8/9/08

Integración con MOSS aprovechando DynamicEntity

Y aprovechando también que he tenido que pensar un poco acerca de las entidades dinámicas en CRM para el proyecto CSP de Microsoft, voy a poner mis ideas en orden. Dentro del proyecto se encuentra una integración con MOSS que incluye Forms Server y por tanto InfoPath. Se trata de un gobierno local o ayuntamiento que quiere gestionar on-line una serie de trámites del ciudadano. En nuestra solución, cada trámite se traduce en una entidad personalizada de CRM que tiene una relación 1:1 con un Caso del módulo de servicio. Por el lado de MOSS, tenemos una serie de formularios InfoPath con los datos específicos de cada trámite. Necesitamos una manera de convertir una cosa en otra: un formulario de InfoPath en un registro de una entidad de CRM. Y aquí es donde entran la DynamicEntity de CRM. La SDK nos da mucho juego al fabricar entidades fuertemente tipadas, pero en este caso nos ataría mucho al tipo concreto de entidad. Además, las DynamicEntities permiten ser serializadas en XML. InfoPath también es XML, así que lo único que necesitamos es una transformación XSL para pasar de un lado al otro:

dynamicentities

En el último paso, un Plug-In se encarga de rellenar los campos que falten y que InfoPath no puede conocer. En este caso, se trata del Guid del ciudadano, que no está explícitamente en el formulario de InfoPath. Una de las ventajas que permite esta aproximación es su genericidad: si se añade un nuevo trámite, en principio, no habría que cambiar una sola linea de código. Sería tan sólo añadir la nueva transformación XSL. Este esquema es bastante flexible y se podría aprovechar para generar cualquier tipo de entidad.

1/9/08

Intellisense para JavaScript de CRM

En este video del muy interesante libro CRM as a Rapid Development Plaform te cuentan (entre otras muchas cosas) cómo hacer el desarrollo de lado cliente para CRM un poco más amistoso. A través de la SDK de metadatos se descarga todo el conjunto de nombres de entidades y atributos y se genera un fichero JavaScript con sus definiciones.

Muy ingenioso y puede ahorrar bastantes quebraderos de cabeza por un literal mal escrito.

15/7/08

CRM como plataforma de desarrollo

Ultimamente se está oyendo hablar mucho de este tema: considerar CRM no sólo como un producto terminado, sino también como unos cimientos sobre los que construir aplicaciones de negocio de cualquier orientación. Siempre que haya clientes implicados y considerando que la capa de plataforma de CRM incluye:

  • Creación de entidades
  • Gestión de relaciones 1-n y n-n entre entidades
  • Generador de Interfaz de Usuario

  • Generador de consultas

  • Seguridad basada en roles
  • Motor de Workflow
  • Trabajo offline

  • Enlace automático con correo electrónico
  • Gestión de eventos en cliente
  • Asistente para reporting
  • Importación/Exportación de datos con Excel

Pues tenemos gran parte de la "fontanería" de la aplicación creada para nosotros. Hace poco participé en un evento de Microsoft en el marco de los Innovation Days sobre Herramientas de desarrollo rápido o RAD de aplicaciones donde presenté este tema.

Podéis descargar las PPTs de mi parte del evento aquí.

Actualización Otro enlace interesante: el Developer Ramp up kit for Microsoft Dynamics CRM 4.0 Contiene Powerpoints, videos y documentos con laboratorios paso a paso para aprender a desarrollar sobre CRM.

23/5/08

Barra de botones de Outlook desaparecida

Este problema es un viejo conocido: un buen día arrancas el Outlook y la barra de botones de CRM ha desaparecido. He encontrado varios apaños, como lo que cuenta en este blog un tal John Bryntze. Dice, en resumen, que hay que:

  • Rehabilitar elementos del Outlook (En Outlook 2003 ir a Ayuda - Acerca ... - Elementos deshabilitados, en Outlook 2007 ir a Ayuda - Elementos deshabilitados)
  • Habilitar el add-in de CRM
  • Salir de Outlook
  • Abrir regedit
  • Ir a la clave HKEY_CURRENT_USER\Software\Microsoft\MSCRMClient
  • Borrar el contenido de la clave UIXmlHash

El contenido de UIXmlHash se volverá a rellenar una vez se haya reiniciado Outlook

23/4/08

Probando LinqtoCRM (y Jing)

Esto es una prueba doble: por un lado utilizo una librería para CRM que permite hacer consultas con Linq: LinqtoCRM. Está en Codeplex y su autor está aquí: http://www.itu.dk/~friism/blog/ Todavía es muy, muy preliminar pero aún así es interesante. Para mi es la excusa para meterme en la jungla de Linq. Por el otro lado, estreno Jing + ScreenCast para subir vídeos de programación, a ver qué tal está la calidad del video. Como es grande para embeberlo en el blog, dejo sólo el enlace:



Enlace al video

Añadir vistas en el SiteMap

Un escenario que me he encontrado a menudo es el del cliente que quiere utilizar una entidad para más de un propósito. Por ejemplo: las cuentas sirven para representar tanto a clientes, proveedores o socios. Las vistas de CRM pueden servir para tener un filtrado rápido según el criterio de tipo de relación, pero aún así hay gente que preferiría que la navegación fuera más explícita, con un enlace en la navegación principal. La nueva característica de Copiar acceso directo de CRM 4.0 nos permite este pequeño truco. Lo que haremos será utilizar un enlace a una vista como una página al mismo nivel que el resto de entidades. Digamos que fabricamos una vista Proveedores y la publicamos.

Ahora navegamos hasta la vista que acabamos de crear y usamos la acción Copiar accceso directo.



Exportamos el fichero de SiteMap para incluir el nuevo enlace. Por cierto, ojo con el carácter de ampersand incluido en la Url de la vista porque puede despistar al cargador de personalizaciones. En resumen: sustituid & por &amp o la personalización no cargará.

.._root/homepage.aspx?etc=1&amp;viewid=%7b60..

Aquí tenéis la muestra del SiteMap con el nuevo enlace. Lo he colocado justo debajo del enlace a Cuentas.


<SubArea Id="nav_accts" Entity="account" DescriptionResourceId="Account_SubArea_Description" />
<SubArea Id="nav_proveedores"
Url="http://10.1.4.129:5555/CRMDemo/_root/homepage.aspx?etc=1&amp;viewid=%7b60ACDB4C-210D-DD11-A3F2-0003FF8B31FE%7d">
<Titles>
<Title LCID="3082" Title="Proveedores" />
</Titles>
</SubArea>


Sólo queda importar el nuevo fichero SiteMap a CRM y ya está: si hacéis Ctrl+F5 tendréis un enlace a una nueva entidad en vuestro CRM.

20/2/08

Cerrar automáticamente actividades de una campaña

Este problema ha surgido varias veces y hasta ahora no se me había ocurrido una solución satisfactoria. Cuando creamos una campaña (express o no) de marketing, si usamos un canal como cartas o tareas, tenemos que tener a una persona (o varias) que vayan cerrando las actividades cerradas. Esto está bien para un telemarketing o una distribución de trabajos entre varias personas.

¿Pero qué pasa si lo que estamos haciendo es un envío masivo de cartas que envía una sola persona y de una sola vez? ¿Le obligamos a irlas cerrando una por una? No existe el Cerrar masivocomo existe la edición masiva. Tampoco podemos lanzar un workflow sobre un conjunto de actividades. Tampoco parece buena opción ni siquiera registrar la acción, al fin y al cabo para eso tenemos la herramienta en un entorno de marketing.

¿Qué se me ha ocurrido? Suena un poco chapuza: crear una regla de Workflow sobre las cartas que, cuando detecta que están asociadas a una campaña, las cierra automáticamente al cabo de un cierto tiempo. ¿Y cómo las detecta? Sólo se me ocurre poner alguna marca en la categoría, por ejemplo MAILING.



Así quedaría una regla de este tipo. Habría que hacer lo mismo para los otros tipos de actividades que lo requieran. Y aleccionar a los usuarios para que se use bien, claro.

1/2/08

Uso de ObjectDataSource contra CRM


Imaginemos que queremos llegar a esto: una página ASPX que permita leer y modificar datos de CRM, por ejemplo de una cuenta de cliente. Hay mil maneras de hacerlo, sólo que esta a mi me parece especialmente elegante por el poco código que hay que añadir. Es una solución al estilo ASP.NET 2.0, así que vamos a trabajar con Visual Studio 2005 (o 2008).
Lo primero es crear el código del componente de datos, que actuará de intermediario entre el control ObjectDataSource y el web service de CRM. La clase se llama Cuenta y representa la capa de lógica de negocio especializada en operaciones sobre cuentas de cliente. Por cierto, ¿alguien sabe cómo colorear sintaxis (facilmente) en Blogger?


using System;
using System.ComponentModel;
using CrmNetLib.CrmSdk;

namespace CrmNetLib
{

[DataObject]
public class Cuenta
{
private CrmService crm;

public Cuenta()
{
crm = new CrmService();
crm.Credentials = System.Net.CredentialCache.DefaultCredentials;
}

[DataObjectMethod(DataObjectMethodType.Select, true)]
public account Buscar(Guid id)
{
return (account)crm.Retrieve(EntityName.account.ToString(),
id, new AllColumns());
}

[DataObjectMethod(DataObjectMethodType.Update, true)]
public void Actualizar(account cuenta)
{
crm.Update(cuenta);
}



}
}




Fijaos en los atributos DataObject y DataObjectMethod. Sirven para que el asistente del ObjectDataSource que pondremos en la página reconozca el objeto como candidato a fuente de datos. Y ahora vamos a la página aspx: lo primero que hacemos es añadir un objeto ObjectDataSource configurando su fuente al objeto comercial (=de lógica de negocio) Cuenta. En mi caso lo he definido en un proyecto aparte de nombre CrmNetLib, pero podría estar en el mismo proyecto, por ejemplo dentro de la carpeta App_Code.




En la siguiente pantalla del asistente, configuramos los métodos de búsqueda y actualización para que apunten a los métodos Buscar y Actualizar de nuestra clase. En un caso más completo, sólo nos faltaría mapear también los métodos de borrado e inserción.









Como el método de búsqueda tiene un parámetro (el id de la cuenta), tenemos que relacionarlo con algún valor de entrada. Escogemos el QueryString de la página, con lo que la URL de acceso tendrá el siguiente aspecto:



http://www.servidor.com/aplicacion/editarcuenta.aspx?id=//GUID de la cuenta//




Por otro lado, el método de actualización recibe un parámetro de tipo account sacado directamente de la SDK de CRM que representa una cuenta con todos sus atributos. También podríamos usar un método que tuviera como parámetros los atributos del objeto, en este caso id, nombre, teléfono y fax.



Ahora sólo queda añadir el control DetailsView para ver los datos de la cuenta. Una vez en la zona del diseñador, escogemos el ObjectDataSource como fuente de datos. Aparecerán todos los campos de la clase account(o sea, muchos campos(.





Para reducirlos a un número manejable, sólo tenemos que escoger la opción Editar campos... Decido quedarme con nombre, teléfono y fax. Si además habilitamos la edición del control, ya habremos terminado en un tiempo record una página de edición de cuentas.

Este es el aspecto final de la página en el diseñador de Visual Studio:




24/12/07

CRM vs. SharePoint

Aparte de parecer el título de una película estilo "Alien vs. Predator", o más que un artículo es una declaración de intenciones de las que se hacen típicamente en estas fechas. Se trata de enumerar las maneras que existen (que se me ocurren) de conectar SharePoint y CRM, de forma más genérica primero y luego con ejemplos concretos en artículos individuales (y aquí es donde empiezan las buenas intenciones).

Integración visual en CRM Podemos añadir un IFRAME a un formulario de CRM que muestre una página de SharePoint. La página en cuestión podría (debería) estar libre de decoraciones estilo SharePoint para que el encaje sea más limpio. Esto se puede hacer fácilmente con SharePoint designer. Por ejemplo: para una cuenta de CRM, mostrar en su formulario una biblioteca de documentos de SharePoint con los documentos asociados al cliente. La URL de SharePoint se puede crear dinámicamente en el evento OnLoad del formulario, y debería incluir información para filtrar la biblioteca, por ejemplo el número de cliente.

CRM List Web Part Muestra una lista de entidades de CRM dentro de SharePoint (WSS o MOSS). También permite (como muchos otros web parts) filtrar detalles de otro web part. Por ejemplo: un Web Part con una lista de cuentas de CRM y otro conectado con los contactos asociados. Se puede descargar aquí y ya lo ha contado (mejor) Marco Amoedo en este post. Sencillo de configurar, pero con pocas funcionalidades y - en principio - no extensible, puesto que el código no está disponible.

Business Data Catalog Dentro de MOSS 2007 tenemos el gran invento del BDC: una herramienta que nos permite ver datos externos a SharePoint integrados como Web Parts. A diferencia del sistema anterior, es un sistema genérico que permite conectar a cualquier base de datos, (digamos Oracle, MySQL etc.) y necesita ser configurado abundantemente a nivel SQL. Para empezar, necesitaremos una Application Definition File específica para la fuente de datos. Hay gente que ha desarrollado la apropiada para CRM, en concreto Rehman Gul en este ejemplo.

Worflows/CallOuts/Plugins de CRM En cualquiera de sus versiones (la más clásica de CRM 3.0 o la nueva de CRM 4.0 integrada con Windows Workflow Foundation) podemos detectar eventos de CRM que actualicen o creen información en SharePoint. En el caso de los wf de CRM 3.0, estos eventos están limitados a la creación, cambio de estado y cambio de propietario. Con los CallOuts/Plugins no tenemos esta limitación. Por ejemplo: cada vez que se crea una cuenta de cliente en CRM, podríamos lanzar un proceso que cree una biblioteca de documentos en SharePoint. Habría que desarrollar la funcionalidad utilizando la SDK de SharePoint en una dll que integrar como una acción del workflow de CRM (versión 3.0). Para la versión 4.0 habría que desarrollar una CustomActivity.

Workflows de SharePoint Buenas noticias, también basados en WF. Tanto en WSS como en MOSS podemos asociar workflows a listas o bibliotecas de documentos. Las acciones elementales del workflow son actividades que pueden incluir cualquier código personalizado, incluyendo llamadas a la SDK de CRM. Por ejemplo: podríamos tener un sitio de SharePoint expuesto en Internet que permita al usuario dar de alta una solicitud de servicio en una lista especializada. El wf asociado a la lista podría crear un incidente en nuestro CRM a través de la SDK. Ventaja: la interfaz de usuario final es la propia de SharePoint. Si estamos en un sitio de "publishing" podemos darle el aspecto que queramos con sólo cambiar el diseño de la página con SharePoint designer.

Web Parts La SDK de WSS permite desarrollar nuestros propios Web Parts, que ahora además están totalmente integrados en ASP.NET 2.0, así que podemos probarlos en cualquier página aspx, no sólamente en un site de SharePoint. La idea es la misma que en el punto anterior, pero más cercano a la interfaz de usuario: el Web Part llama a la SDK de CRM directamente, sin que haya un almacenamiento en SharePoint. El ejemplo: el mismo del punto anterior, pero sin lista en SharePoint. La pega: tenemos que diseñar la interfaz de usuario directamente en código (C# o VB) sin un editor visual a través de un CustomControl de ASP.NET 2.0

Servicio Windows Esta es la versión asíncrona de algunas de las opciones que hemos visto. La idea es tener relacionados una entidad de CRM (digamos Artículos de la KB) con una lista/biblioteca de SharePoint. Un servicio periódico utlizaría ambas SDKs para sincronizar los elementos concordantes. Habría que añadir una columna a la lista con el GUID de CRM y un campo en la entidad de CRM con el identificador de SharePoint para hacerlo posible.

15/12/07

Mi primer Redeployment/Reimplementación de CRM 3.0

Suena como un anuncio de colonia, ya lo sé. El caso es que me he encontrado con algunos problemillas (qué sorpresa) y aquí van, para que no se olvide. En líneas generales, el proceso que se explica en la carpeta RedeploymentTool es correcto. Sólo añadir que:
  • El backup de la base de datos original no debería incluir los usuarios (SQLAccessGroup y demás) que crea la instalación de CRM. Estarán basados en el directorio activo antiguo. Aunque se vaya a instalar sobre el mismo directorio activo, no conviene que estén, pues la instalación intentará volver a crearlos y dará un error, poco explicativo, por cierto de tipo GrantDatabaseAccessAction.
  • Puede que dé un error AddLicenseAction. Se puede omitir, como recomienda Matt Parks aquí, pero también se puede entrar a la base de datos, tabla Licenses, y cambiar (por ejemplo) una letra de la clave de activación. El programa de instalación machacará esta tabla, en cualquier caso.
Por lo demás, todo fue bien, aunque me sorprendió no encontrar demasiada información en la web sobre Redeployment. Es una herramienta fundamental para fabricar entornos de prueba.

14/12/07

Plantillas de correo y "slugs"

Aviso que esta entrada es de las de "no te olvides de...". Se trata de las plantillas de correo electrónico, una herramienta muy potente de CRM que, sin embargo, tiene sus limitaciones. En principio podemos copiar cualquier HTML a nuestra plantilla con un copiar y pegar. Todo va bien hasta que intentas copiar estilos que hayan sido definidos en la cabecera. Por ejemplo:

<style type="text/css">
H1 {border-width:1;border:solid;text-align:center}
</style>

La primera en la frente: el "copiar y pegar" tan sólo copia la parte de BODY, no la de HEAD. Así que tendremos que utilizar el estilo directamente en cada etiqueta:

<h1 style="border-width:1;border:solid;text-align:center">Titulo</h1>

Vamos con otra: los slugs. Los slugs son cadenas que permiten introducir campos de la entidad en el texto de la plantilla. Por ejemplo, para insertar el nombre de un contacto, podemos poner

Estimado {!contact:firstname;}

Un cliente pensó (con razón) que sería interesante poner un enlace en HTML a una aplicación web en la plantilla. Algo como

<a href='http://www.emp.com/registro.aspx?id={!account:accountid;}>Registro online</a>

Como véis, hemos metido un slug dentro de una etiqueta de código HTML. Además, hemos escogido el campo accountid que es un GUID que en la interficie de usuario está oculto. ¿Funciona esto? Pues sí y no. Para empezar, no creo que esté soportado. De hecho, el editor de HTML de la plantilla da errores a veces imprevisibles. Lo que he aprendido (a base de golpes) es que, por ejemplo, si en lugar de poner comillas simples (como en el ejemplo anterior) ponemos comillas dobles el editor falla y no podemos guardar la plantilla. Es sólo un ejemplo de los problemas que puede dar. Pese a todo, merece la pena echarle un vistazo.

29/10/07

Prefiltrado en infomes

Aquí va una pequeña advertencia y recordatorio - sobre todo para mi mismo - acerca del prefiltrado de informes en CRM. Esta característica es muy, muy útil, ya que te permite reutilizar un mismo informe de muchas maneras, tan sólo cambiando el filtrado. Este prefiltrado se gestiona desde CRM - no desde el diseño del informe - a través de la interfaz habitual de búsqueda avanzada. De esta forma, un informe de Incidentes cerrados la última semana se puede convertir en Incidentes cerrados para clientes con más de 500 empleados el último año o cualquier otra cosa que nos permita hacer la búsqueda avanzada.

En principio lo unico que hay que hacer (cito el MOC de personalización) es ponerle a las entidades que queramos prefiltrar un alias que comience por CRMAF_ . En el siguiente ejemplo, he escogido como alias CRMAF_FI para representar los casos, o sea la vista FilteredIncident.


SELECT CRMAF_FI.ticketnumber, CRMAF_FI.customeridname
FROM FilteredIncident AS CRMAF_FI


Este informe permitirá prefiltrar por atributos del caso. Lo podemos complicar un poco más añadiendo la tabla de cuentas:


SELECT CRMAF_FI.ticketnumber, CRMAF_FA.name
FROM FilteredIncident AS CRMAF_FI
INNER JOIN FilteredAccountt AS CRMAF_FA
ON FilteredIncident.customerid = FilteredAccount.accountid


Esto nos permitirá prefiltrar por caso y/o cuenta. Bueno, pues aquí viene la advertencia: el editor de informes integrado en Visual Studio añade delante de cada vista o tabla un dbo. que hace que deje de funcionar el prefiltrado. Por ahora, se lo tengo que quitar a mano con un Reemplazar, porque no he encontrado la opción para evitar este comportamiento.

25/10/07

Crear un contrato por código

Admitámoslo, crear un contrato en CRM es una pesadez. Al menos eso es lo que dicen algunos de los usuarios del módulo de servicio que conozco. Hay que seleccionar el cliente, crear el contrato, establecer la plantilla de contrato, fechas de inicio y finalización (cuatro en total), las direcciones (dos), añadir la línea de contrato, volver a poner las fechas de inicio y finalización, establecer el precio...

Bueno, pues todo esto es lo que hace este tocho código que véis aquí debajo. No es perfecto, pero puede ahorrar algo de tecleo.

public static Guid CrearContrato(
Guid idCuenta,
Guid idPlantillaContrato,
int numeroMinutos,
int importe)
{
// Buscar los datos del cliente
ColumnSet cs = new ColumnSet();
cs.Attributes = new string[] { "name", "address1_addressid" };
account cuenta =
crm.Retrieve(EntityName.account.ToString(), idCuenta, cs) as account;

// Buscar los datos de la plantilla
contracttemplate plantilla =
crm.Retrieve(EntityName.contracttemplate.ToString(), idPlantilla,
new AllColumns());

// Crear un título para el contrato
string tituloContrato = String.Format(
"{0} - {1} {2} min ({3:MM/yy})",
cuenta.name, plantilla.name, numeroMinutos, DateTime.Now);

// Establecer fechas (contrato de 10 años de duración)
string fechaInicio =
DateTime.Now.ToString("yyyy/M/ddT00:00:00");
string fechaFin =
DateTime.Now.AddYears(10).ToString("yyyy/M/ddT00:00:00");

// Crear el contrato
contract contrato = new contract();
contrato.contracttemplateid = new Lookup();
contrato.contracttemplateid.Value = idPlantillaContrato;
contrato.customerid = new Customer();
contrato.customerid.Value = idCuenta;
contrato.customerid.type = EntityName.account.ToString();
contrato.billingcustomerid = contrato.customerid;
contrato.activeon = new CrmDateTime();
contrato.activeon.Value = fechaInicio;
contrato.expireson = new CrmDateTime();
contrato.expireson.Value = fechaFin;
contrato.billingstarton = new CrmDateTime();
contrato.billingstarton.Value = fechaInicio;
contrato.billingendon = new CrmDateTime();
contrato.billingendon.Value = fechaFin;
contrato.serviceaddress = new Lookup();
contrato.serviceaddress.Value = cuenta.address1_addressid.Value;
contrato.billtoaddress = new Lookup();
contrato.billtoaddress.Value = cuenta.address1_addressid.Value;
contrato.title = tituloContrato;
Guid idContrato = crm.Create(contrato);

// Crear la línea de detalle del contrato
contractdetail linea = new contractdetail();
linea.title = "Linea " + tituloContrato;
linea.contractid = new Lookup();
linea.contractid.Value = idContrato;
linea.activeon = new CrmDateTime();
linea.activeon.Value = fechaInicio;
linea.expireson = new CrmDateTime();
linea.expireson.Value = fechaFin;
linea.customerid = new Customer();
linea.customerid.type = EntityName.account.ToString();
linea.customerid.Value = idCuenta;
linea.totalallotments = new CrmNumber();
linea.totalallotments.Value = numeroMinutos;
linea.price = new CrmMoney();
linea.price.Value = importe;
Guid idLinea = crm.Create(linea);

return idContrato;
}

4/10/07

Accediendo a CRM con ASP.NET 2.0

Se puede (?) ver aquí debajo mi primera prueba de video blogger sobre programación.




Se trata de llamar a los web services de CRM a través de un ObjectDataSource, que permite encapsular los comandos SELECT, UPDATE, INSERT y DELETE típicos en un objeto de lógica de negocio. Para el caso, se han mapeado a los métodos Retrieve, Update, Create y Delete del CrmService. Luego se muestran los resultados con DataBinding a un control DetailsView, que permite ver todos los campos de un registro.

23/7/07

Llamar a web services de CRM desde JavaScript

En este thread de las news de CRM en español firmado por Nuria he encontrado un enlace a un blog donde se cuenta una alternativa cómoda para llamar a los web services de CRM desde JavaScript en cliente. En concreto, se trata de cómo ocultar/mostrar campos de un formulario en función de los roles a los que pertenece el usuario.

La entrada de blog es de Ronald Lemmen y se encuentra aquí.

Muy chulo (¡pero ojo, no soportado!)

25/5/07

Autonumeración "a la carta" en CRM

Todos hemos visto cómo Microsoft CRM genera códigos automáticos para unos pocos tipos de registros: Contratos, Ofertas, Pedidos, Facturas y artículos de la Knowledge Base. ¿Qué podemos hacer si necesitamos que, además de estas entidades, alguna otra se autonumere también? Pues recurrir a la personalización del formulario correspondiente haciendo que llame a un pequeño servicio que programaremos en el servidor.

Empezaremos por programar el servicio de autonumeración. Abrimos Visual Studio y generamos un proyecto web sobre la misma carpeta en la que tenemos el servidor de CRM. En este caso, la he llamado autonum.



Para que nuestro proyecto web conecte con el servidor de CRM, sólo tenemos que añadir una referencia Web a su Web service, que como sabéis está en

http://maquina:puerto/MSCRMServices/2006/crmservice.asmx


Le damos un nombre apropiado a la referencia, por ejemplo CrmSdk.



Ahora, creamos un nuevo WebForm y le damos el nombre default.aspx Esta será la página que nos proporcionará los números correlativos para nuestra entidad. Aquí tenéis el código:


void Page_Load(object sender, System.EventArgs e){
string entidad = Request["e"];
string campo = Request["c"];
string prefijo = Request["p"];
string numCars = Request["n"];
string formato = "{0}{1:D" + numCars + "}";
string anterior = UltimoCodigo(entidad, campo);
string parteNumerica = anterior.Substring(prefijo.Length);
int num = 1 + Int32.Parse(parteNumerica);
Response.Write(String.Format(formato, prefijo, num));
}

string UltimoCodigo(string entidad, string campo) {
string fetch = @"<fetch count='1' mapping='logical'>
<entity name='{0}'>
<order attribute='createdon' descending='true'/>
<attribute name='{1}'/>
</entity>
</fetch>";
fetch = String.Format(fetch, entidad, campo);
CrmService crm = new CrmService();
crm.Credentials = System.Net.CredentialCache.DefaultCredentials;
string res = crm.Fetch(fetch);
XmlDocument dom = new XmlDocument();
dom.LoadXml(res);
XmlNode nodoCuenta = dom.SelectSingleNode("//" + campo);
if (nodoCuenta != null)
return nodoCuenta.FirstChild.Value;
else
return "?";
}



Como véis, lo único que hace es lanzar una consulta FetchXml que busca la última entidad creada (ordenando por el campo createdon) y extrae su código, que puede ser cualquier atributo que escojamos. En el ejemplo, la entidad es account y el campo accountnumber.

Ahora tenemos que personalizar el evento OnLoad de la entidad para que, al cargarse un nuevo formulario, se conecte a nuestra página en el servidor y rellene su valor en el campo que hayamos escogido. Vamos a Configuración - Personalización y escogemos la entidad Cuenta. Luego, escogemos su formulario y abrimos sus propiedades:




El código Javascript utiliza una conexión al estilo AJAX con el objeto XmlHttp. Como la llamada es asíncrona, no bloqueará el formulario para la introducción de otros datos. Aquí tenéis el código:

if (crmForm.FormType != 1)
return;
var entidad = "account";
var campo = "accountnumber";
var prefijo = "A-";
var numCars = 4;
var xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
var params = "?e=" + entidad +"&c=" + campo + "&p=" + prefijo + "&n=" + numCars;

xmlHttp.open("GET", "/autonum" + params, true);
xmlHttp.setRequestHeader('Content-Type', 'text/xml');
xmlHttp.send('');

xmlHttp.onreadystatechange=function() {
if(xmlHttp.readyState == 4 && xmlHttp.status == 200)
{
var objDoc = xmlHttp.responseText;
if( xmlHttp.responseText.length > 0 )
crmForm.accountnumber.DataValue = xmlHttp.responseText;
}
}

A continuación, publicamos las personalizaciones y nos vamos a la lista de cuentas.




La última cuenta cread tiene el código A-0126. Hemos configurado los parámetros de la llamada para que considere el prefijo A- y utilice 4 dígitos en el código. Cuando le damos al botón Nuevo ...



Aparece la ventana de creación de nuevas cuentas, pero esta vez con el siguiente número de cuenta rellenado automáticamente. ¡Conseguido!