mardi 3 avril 2012

Envoyer des emails en APEX

La classe Messaging fournie par APEX permet d'envoyer des emails facilement, qu'ils contiennent du texte brut, du contenu HTML ou qu'ils reposent sur des modèles de message.
Nous allons, dans cet article, montrer deux méthodes implémentant les fonctionnalités de la classe Messaging.

Envoi d'un email en texte brut ou HTML

La méthode ci-dessous permet d'envoyer un email en spécifiant l'objet du message, son contenu et les destinataires.

public static void sendEmailWithText(String the_Object, String the_Text, Boolean the_IsHtml, String[] the_Recipients) {
                       
   // Check the limit of sent emails is not passed
   if (Limits.getEmailInvocations() >= Limits.getLimitEmailInvocations()) {
      return;
   }
                       
   Messaging.SingleEmailMessage a_Mail = new Messaging.SingleEmailMessage();
           
   if (the_Recipients != null) {
      // Set the destinary adresses
      a_Mail.setToAddresses(the_Recipients);
   }
   else {
      // Find the email of the current user
      User a_User = [SELECT Email 
                     FROM User 
                     WHERE Id=:Userinfo.getUserId()];
        
      // Set the destinary adresses
      a_Mail.setToAddressesnew String[] {a_User.Email );
                        
      if ((the_Object == null) || (the_Object == '')) {
         a_Mail.setSubject('NaTwelve : Custom email sent');
      }
      else {
         a_Mail.setSubject(the_Object);
      }
                       
      // Set the destinary adresses
      if (the_IsHtml) {
         a_Mail.setHtmlBody(the_Text);
      }
      else {
         a_Mail.setPlainTextBody(the_Text);
      }
       
      // Do not create an email task for this email
      a_Mail.setSaveAsActivity(false);
           
      // Send the email
      Messaging.sendEmail(new Messaging.SingleEmailMessage[] { a_Mail });
   }
}


Comment utiliser notre méthode ?

Avec un texte brut
sendEmailWithText('Objet de mon email', 'Corps de mon email\nEmail au format texte brut.', false, System.Userinfo.getUserId());


Avec de l'HTML
sendEmailWithText('Objet de mon email', '<h1>Corps de mon email</h1><br/>Email au format HTML.', true, System.Userinfo.getUserId());

Cette méthode peut être utilisée aussi bien pour envoyer des alertes, que des notifications ou des traces.
J'utilise, moi-même, cette fonction dans de nombreux services de messagerie afin de recevoir des traces de debug au format texte par email.

Envoi d'un email avec modèle de message

public static void sendEmailWithTemplate(String the_Template, Id the_RecipientId, Id the_ObjectId) {

                       
   // Check the limit of sent emails is not passed
   if (Limits.getEmailInvocations() >= Limits.getLimitEmailInvocations()) {
      return;
   }
                    
   // Get the email template
   EmailTemplate a_Template = [SELECT Id 
                               FROM EmailTemplate 
                               WHERE DeveloperName =:the_Template];
    
   Messaging.SingleEmailMessage a_Mail = new Messaging.SingleEmailMessage();
                       
   if (the_RecipientId != null) {
      // Set the destinary adresses
      a_Mail.setTargetObjectId(the_RecipientId);
   }
   else {
      // Set the destinary adresses of the current user
      a_Mail.setTargetObjectId(Userinfo.getUserId());
   }
                                  
   // Do not create an email task for this email
   a_Mail.setSaveAsActivity(false);
                                  
   // Set template of email
   a_Mail.setTemplateId(a_Template.Id);
                                  
   if (the_ObjectId != null) {
      // Set the resource demand object for merging fields
      a_Mail.setWhatId(the_ObjectId);
   }
                                  
   // Send the email
   Messaging.sendEmail(new Messaging.SingleEmailMessage[] { a_Mail });
}

Comment utiliser notre méthode ?

Account aAccount = [SELECT Id FROM Account LIMIT 1];
sendEmailWithTemplate('modeleFicheClient', System.Userinfo.getUserId(), aAccount.Id);

En utilisant des modèles de message, on gagne en généricité et on facilite la gestion du contenu des messages qui peut être fait par l'interface du CRM sans avoir à manipuler du code APEX.


Remarques

L'envoi d'email et les limites

Comme on peut le voir en début de nos deux méthodes, on vérifie que la limite EmailInvocations n'est pas dépassée. Force.com limite le nombre d'emails envoyer via la fonction Messaging.sendEmail à 10. Si vous ne respectez pas cette limite, vous verrez ce beau message d'erreur « Too many Email Invocations ».

D'où l'intérêt de grouper les destinataires pour l'envoi d'un même email ou l'utilisation des emails en masse pour l'envoi de contenus différents.

Les emails en masse

Nous avons utilisé la classe SingleEmailMessage dans nos deux méthodes mais il existe aussi la classe MassEmailMessage qui est plus approprié pour l'envoi de messages différents à plusieurs destinataires.
Elle sera particulièrement utile pour les emails qui utilisent des modèles de message. Le modèle sera alors appliqué à chacun des utilisateurs passés à la méthode setTargetObjectIds.

Sachez que l'envoi d'emails est soumis à deux autres limites mais plus rares cette fois-ci. La première empêche l'envoi à plus de 250, 500 ou 1000 (en fonction de l'édition de Salesforce que vous possèdez) destinataires différents et externes pour un même email en masse. La seconde fixe un maximum de 1000 emails envoyés en utilisant la classe SingleEmailMessage ou la classe MassEmailMessage par jour.


source : Outbound Email, Understanding Execution Governors and Limits

vendredi 30 mars 2012

Utilisation des Formules pour gérer nos images

Il est souvent utile d’insérer une image dans une fiche d’un objet ou dans une page Visualforce personnalisée.

Avec les formules de Salesforce et la fonction IMAGE ceci devient très simple. De plus les formules permettent de rendre les images dynamiques, par exemple en fonction d’un statut en particulier ou pour avoir une représentation graphique de l’avancement d’un projet.

Comment ça marche ?

Il faut utiliser la fonction IMAGE


IMAGE(image_url, alternate_text, IMAGE_HEIGHT, IMAGE_WIDTH)


Celle-ci va nous permettre d’insérer le code Html juste en précisant l’url et la taille de l’image.

Par exemple :

IMAGE("/img/samples/flag_green.gif", "Vert", 120, 120)

Ceci va nous générer le code HTML suivant :

<img src="/img/samples/flag_green.gif" alt="vert" height="120px" width="120px” border="0" />

Quelques conseils

  • Les paramètres IMAGE_HEIGHT et IMAGE_WIDTH permet de spécifier la taille d’affichage de notre image en pixels. Si l’on souhaite afficher l’image à sa taille réelle, il suffit d’omettre ces deux paramètres. Par exemple :

IMAGE("/img/samples/color_green.gif", "Carré vert de1px")
IMAGE("/img/samples/color_green.gif", "Carré vert de 50px*50px", 50, 50)
  • Si vous le souhaitez, vous pouvez utiliser les images que vous avez stockées dans l’onglet Documents de SalesForce en utilisant la fonction comme ceci :   
    IMAGE("servlet/servlet.FileDownload?File="id de l’image") 
"id de l’image" est l’identifiant unique du document, par exemple 015x00000000f7e, vous pouvez retrouver votre identifiant dans l’url du navigateur lorsque vous ouvrez la fiche detail d’un document.

Le type de champ Formule 

Dans un objet Salesforce, nous pouvons utiliser un champ de type formule. En fait, c’est un champ en lecture seule, dont la valeur provient d'une formule. Cette formule peut faire référence à un autre champ de l’objet et sera donc mis à jour dès que celui-ci sera modifié. Utile pour créer des images dynamiques !

Voici quelques exemples d’images dynamiques

  • Les drapeaux de différente couleur : Pour indiquer la priorité d’une tache.
IMAGE(
    CASE( {!priority},
        "Low", "/img/samples/flag_green.gif",
        "Medium", "/img/samples/flag_yellow.gif",
        "High", "/img/samples/flag_red.gif",
        "/s.gif"
    ),
    "priorityflag"
)
  • Les feux de circulation vert/jaune/rouge : Pour indiquer le statut d'un projet.

IMAGE(
    CASE( {!Project_Status__c},
        "Green", "/img/samples/light_green.gif",
        "Yellow", "/img/samples/light_yellow.gif",
        "Red", "/img/samples/light_red.gif",
        "/s.gif"
    ),
    "status color"
)
  • Note de 1 à 5 étoiles : Pour indiquer une note ou un score.
IMAGE( CASE( {!Rating__c},
        1, "/img/samples/stars_100.gif",
        2, "/img/samples/stars_200.gif",
        3, "/img/samples/stars_300.gif",
        4, "/img/samples/stars_400.gif",
        5, "/img/samples/stars_500.gif",
        "/img/samples/stars_000.gif"
    ),
    "rating"
)
  • Barre de progression : Ici, la taille des images est calculée en fonction du pourcentage du temps réalisé sur le temps planifié d’une tache.
                    

/* Si > 100% */
IF( {!Temps_Realise__c} / {!Temps_Planifie__c} >= 1 ,
   /* On affiche la partie de 0 à 100 % en vert */
   IMAGE(
      "/img/samples/color_green.gif",
      "Done",
      15,
      ROUND(
         /* La partie verte ne peut pas etre superieur a 100% */
         MIN(100, ( {!Temps_Realise__c} / {!Temps_Planifie__c} ) * 100) / ( {!Temps_Realise__c} / {!Temps_Planifie__c} ),
         0
      )
   )
   /* On affiche la partie de 100 à 125 % en jaune */
   & IMAGE(
      "/img/samples/color_yellow.gif",
      "Passed",
      15,
      ROUND(
         /* Le partie jaune doit etre positive et ne doit pas depasser 25% */
         MIN(25, MAX(0, (( {!Temps_Realise__c} / {!Temps_Planifie__c} ) * 100) - 100)) / ( {!Temps_Realise__c} / {!Temps_Planifie__c} ),
         0
      )
   )  
   /* Et au dessus de 125 % en rouge*/
   & IMAGE(
      "/img/samples/color_red.gif",
      "Passed",
      15,
      ROUND(
         /* La partie rouge doit etre positive */
         MAX(0, (( {!Temps_Realise__c} / {!Temps_Planifie__c} )*100) - 125) / ( {!Temps_Realise__c} / {!Temps_Planifie__c} ),
         0
      )
   )
    /* On affiche le pourcentage sous forme de texte apres les bandes de couleurs */
   & " " & TEXT( ROUND(( {!Temps_Realise__c} / {!Temps_Planifie__c}  )*100, 0) ) & "%",
/* Sinon */
    /* On affiche que la partie en vert de 0 à 100 % */
    IMAGE(
      "/img/samples/color_green.gif",
      "Done",
      15,
      ROUND(
         ( {!Temps_Realise__c} / {!Temps_Planifie__c} ) * 100,
         0
      )
   )
    /* On affiche le pourcentage sous forme de texte apres les bandes vertes */
    & " " & TEXT( ROUND(( {!Temps_Realise__c} / {!Temps_Planifie__c} ) * 100, 0) ) & "%"
)

Toutes ces formules utilisent des champs de l’objet dans un case afin d’afficher la bonne image en fonction de la valeur du champ.

A vos formules ;)


source : Image Formula Fields Customization Guide 

mardi 27 mars 2012

La puissance des requêtes SOQL

Le langage SOQL ou Salesforce Object Querying Language permet d'interroger la base de données de Force.com via des requêtes qui ne sont pas sans rappeler les requêtes SQL.

Le petit frère de SQL ?

Nous allons noter quelques différences majeures entre les langages SOQL et SQL :

Tout d'abord, il est impossible de sélectionner tous les champs d'une table en utilisant l'opérateur " * ". Vous ne verrez donc jamais cette requête :

[SELECT * FROM Account];

Ensuite, le langage SOQL ne peut être utilisé que pour extraire des données de la base de données, soit avec le mot-clé "SELECT". Pour tout ce qui est CRUD, il faudra alors se tourner vers les instructions DML que nous aborderons dans un prochain article.

Adieu CONCAT, UPPER, DATEDIF ou autre opérateur "+"... Hélas, SOQL est très pauvre en fonction et opérateurs. Et ne comptez pas utiliser des alias non plus.
On peut toutefois bénéficier des opérateurs suivants :
  • =, <, , > et pour comparer nombres et dates
  • =, LIKE (et son meilleur ami "%") pour comparer des chaînes de caractère
Il est important de noter qu'il est impossible de comparer deux champs entre eux dans une requête. La comparaison doit obligatoirement se faire entre un champ et une constante. Vous ne verrez donc jamais une requête de ce genre :

[SELECT Subject 
 FROM Event 
 WHERE EndDatetime > StartDatetime + 10];

Attendez ! Je vous vois venir... Vous allez me dire que cet article s'intitule "La puissance des requêtes SOQL" alors que jusqu'à maintenant ce que nous avons vu fait plus penser à de l'impuissance. Mais SOQL c'est aussi la capacité à extraire des données des tables parents et enfants en toute simplicité.

Un langage [très] relationnel

Les champs de types Lookup ou Master-detail permettent d'avoir des relations 1-n entre les tables de la base de données. Un exemple est le lien entre les tables Compte et Opportunité.
Comme c'est la référence du compte qui est stockée dans un champ de l'opportunité, nous disons que la table Compte est une table parent de la table Opportunité et, inversement, la table Opportunité est une table enfant de la table Compte.
En SQL, nous parlons de jointure droite ou jointure gauche.

Relation avec une table parent

Jointure externe droite
Ce type de jointure permet d'extraire des données d'une table parent.
Exemple : 
[SELECT Name, Position__r.Department__c FROM Job_Application__c];

Jointure interne droite
Cette jointure rajoute un filtre supplémentaire sur le résultat de la requête. Il faut que chaque enregistrement soit rattaché à un enregistrement parent.

Exemple :
[SELECT Name
 FROM Job_Application__c
 WHERE Position__r.Department__c=‘Sales’];

Anti-jointure droite
L'anti-jointure permet de ne pas extraire les éléments pour lesquels un enregistrement parent existe.

Exemple :
[SELECT Name FROM Job_Application__c WHERE Position__c = null];

Attention : Salesforce impose une limite de 25 relations vers des champs de tables parent.

Relation avec une table enfant

Jointure externe gauche
Ce type de jointure permet d'extraire des données d'une table enfant.

Exemple :
[SELECT Name, (SELECT Name FROM Job_Applications__r)
 FROM Position__c];

Jointure interne gauche
Cette jointure rajoute un filtre supplémentaire sur le résultat de la requête. Il faut que chaque enregistrement possède des enregistrements enfant

Exemple :
[SELECT Name
 FROM Position__c
 WHERE Id IN (SELECT Position__c FROM Job_Application__c)];

 
Anti-jointure gauche
L'anti-jointure permet de ne pas extraire les éléments pour lesquels il n'y a pas d'enregistrement enfant.

Exemple : 
[SELECT Name 
 FROM Position__c
 WHERE Id NOT IN (SELECT Position__c FROM Job_Application__c)];
Attention : Le nombre de tables enfant extraites est de 20 maximum par requête. Il est possible d'utiliser d'autres requêtes SOQL pour extraire les tables enfant restante, en faisant attention à la limite « Too many SOQL Queries ».


Le langage SOQL c'est tout ça, et bien plus encore grâce aux résultats globaux offrant COUNT, GROUP BY et autre HAVING, mais aussi grâce aux requêtes dynamiques et la généricité qu'elles apportent.

source: A Deeper look at SOQL and Relationship Queries on Force.com