Salesforce
Un générateur de devis Salesforce sans licence CPQ : ce qu'on a construit, ce qu'on a appris
par Paul Mantez, Co-fondateur & Développeur
Un client qui vend des prestations d'hospitalité et des espaces événementiels ne fait pas des devis comme un éditeur de logiciel. Une offre, chez lui, c'est une loge, un traiteur, un parking, du digital, organisés en blocs avec un sous-total chacun, un acompte à la signature, un solde avant l'événement, et un PDF que le client final lit sans se poser de question sur l'outil qui l'a produit.
L'objet Quote standard de Salesforce ne sait pas faire ça. Il aligne des lignes. Salesforce CPQ, ou Revenue Cloud aujourd'hui, sait le faire et bien plus, avec une licence par utilisateur et une complexité de configuration qui se justifie quand le catalogue est combinatoire. Entre les deux, il y a un espace que beaucoup d'entreprises occupent avec des devis Word. Ce client-là avait déjà tenté deux fois de le combler avec des développements maison. On a industrialisé la troisième.
Ce qu'on a construit
Un composant LWC unique, en mode création ou édition selon qu'il reçoit un identifiant de devis, qui déroule six étapes.
Le client. Le compte, le contact destinataire du devis et, s'il est différent, le contact de facturation. Une porte d'entrée depuis le compte ou depuis l'opportunité, sans jamais réécrire l'opportunité de contexte.
Les sections et les produits. C'est le cœur. Chaque devis s'organise en sections thématiques avec un sous-total, dans lesquelles on glisse des produits depuis un catalogue de variantes. Une ligne peut porter sa propre remise et son propre taux de TVA.
Les conditions. Remise globale en pourcentage ou en montant, validité, conditions de paiement, notes internes qui ne sortent pas sur le PDF.
L'échéancier. Des jalons de facturation exprimés en pourcentage du TTC avec une date chacun, qui suivent ensuite un cycle : en attente, facturé, payé. C'est ce qui permet à la comptabilité de savoir, depuis le devis, ce qui reste à facturer.
L'aperçu, fidèle au rendu final. Puis le PDF, généré par une page Visualforce, stocké et rattaché au devis.
Autour de ce parcours, trois briques qu'on n'avait pas prévues au départ et qui ont fini par peser autant que le parcours lui-même.
Les modèles de devis : un modèle pré-remplit sections, produits et jalons. Un devis réussi peut être sauvegardé comme modèle. Le compteur d'utilisation dit lesquels servent vraiment.
Le catalogue et les règles de prix. Un gestionnaire de produits et de variantes, des prix contractuels par compte, et un moteur déclaratif : une règle porte des conditions (famille de produit, type de compte, montant du devis, quantité) et des actions (nouveau prix unitaire, remise). Les règles s'évaluent dans un ordre défini sur chaque ligne au moment du calcul. L'administrateur crée ses règles sans écrire d'Apex.
La TVA multi-taux. Le client vend des billets à 5,5 % et des prestations à 20 % sur le même devis. La ventilation par taux est recalculée depuis les lignes, jamais stockée, par une classe unique partagée entre l'Apex et le JavaScript du composant, pour que l'aperçu et le PDF somment au centime près.
Le PDF : laisser le client faire le sien
Un générateur de devis vit ou meurt sur son PDF. Le nôtre repose sur une page Visualforce rendue en PDF, mais avec une idée qui a changé la relation avec le client : le template n'est pas figé dans le package.
Un contrôleur expose toutes les données formatées d'un devis : compte, contacts, sections avec leurs lignes, jalons, totaux, ventilation de TVA, mentions légales de l'entité émettrice. Le client crée sa propre page Visualforce dans son org, la branche sur ce contrôleur, et la déclare dans un enregistrement de métadonnées personnalisées. Elle apparaît alors dans le sélecteur de template du générateur. Sur ce projet, le client a ainsi reproduit ses deux PDF historiques, hospitalité et événementiel, sur la nouvelle couche de données, sans toucher au package.
Ce contrat d'API est documenté et considéré comme stable : tout renommage est un changement cassant, tracé dans le journal des versions.
Le packaging : installer par-dessus l'existant
On aurait pu déployer les métadonnées directement dans l'org du client. On a préféré un package, pour les mises à jour propres, le versionnage et l'installation en un clic sur la sandbox puis la production. Restait à choisir entre managed et unlocked.
Unlocked, sans namespace. Le contexte est du conseil mono-client, sans distribution sur l'AppExchange ni besoin de protéger le code. Surtout, un managed aurait imposé une API global figée à vie pour la page Visualforce du client, et il aurait échoué à l'installation sur un point décisif : l'org contenait déjà un objet section, un champ de contact de facturation, un numéro de devis, une liaison ligne vers section. Le client avait construit tout ça pour ses deux outils maison précédents, et ces objets portaient des données de production.
Un package unlocked peut adopter ces métadonnées : il s'installe par-dessus, en prend possession, sans rien supprimer. On a aligné les noms de relation du package sur ceux du client pour que son code historique continue de fonctionner, listé explicitement chaque adoption dans un fichier vérifié par un script d'audit de compatibilité, et écrit un script de reprise pour remplir les nouveaux champs depuis les anciens. Le dry-run sur une sandbox copie de la production a confirmé : 528 sections legacy intactes, 1 713 lignes rattachées, les triggers du client toujours actifs.
La contrepartie, écrite en tête du runbook : le package est désormais propriétaire de l'objet section. Le désinstaller supprimerait les données. Interdiction absolue sans export préalable.
Ce que le packaging a révélé
Le code passait tous ses tests sur l'org de développement. La construction du package se fait dans une org vierge, et c'est là que les bugs de portabilité apparaissent. Quelques-uns, pour vous épargner les journées correspondantes.
Aucun droit sur les champs dans l'org de build. Une requête SOQL dynamique filtrée par accessibilité, qui déréférence ensuite ses champs en dur, plante uniquement au packaging. En développement, l'utilisateur porte les permission sets. Pas dans l'org de build.
Le moteur PDF dépend de l'org. Une feuille de style chargée depuis une ressource statique fonctionne sur une org et échoue en silence sur une autre. Un pseudo-élément avec content vide, le classique clearfix, est rejeté. Pour une page PDF packagée : CSS inliné, CSS 2.1 conservateur, et jamais de génération de PDF dans la même transaction qu'une écriture en base.
Un enregistrement de métadonnées personnalisées mal nommé se déploie sans erreur en source, passe la construction du package, et fait échouer l'installation. L'asymétrie rend le piège invisible jusqu'à la première install chez le client.
Les écritures de masse réveillent les automatisations du client. Une mise à jour de deux mille lignes de devis a déclenché un flow client par ligne et dépassé la limite de temps processeur. Scripts de migration par lots de 250, idempotents, relancés jusqu'à zéro restant, avec une somme de contrôle avant et après.
Et une leçon qui n'a rien de technique : sur les lignes historiques, la valeur de TVA « 0 » signifiait « non renseigné », pas « exonéré ». Les 331 lignes concernées portaient des tarifs réels mixtes. On les a exclues de la migration automatique et documentées, plutôt que d'inventer un taux.
Quand ne pas construire
On ne recommande pas ce chemin à tout le monde. Un moteur de devis maison tient bien avec un catalogue de quelques centaines de variantes, des règles de prix qu'on peut énoncer en une phrase chacune et une équipe capable de faire évoluer un package. Il cesse d'être raisonnable quand le catalogue devient combinatoire, quand les contrats se renouvellent avec des amendements, ou quand la tarification par paliers se négocie à l'échelle d'un réseau de distribution. À ce niveau, CPQ ou Revenue Cloud coûtent moins cher que l'équipe qui maintiendrait l'équivalent.
Entre les deux, il y a cet espace où le devis standard ne suffit pas et où la licence ne se justifie pas. C'est là qu'on construit.
Parlons de vos devis dans Salesforce →
Questions fréquentes
Quand faut-il quand même prendre Salesforce CPQ ou Revenue Cloud ?
Dès que le catalogue devient combinatoire : bundles imbriqués, options conditionnelles, contrats à renouvellement avec amendements, tarification par paliers négociés à grande échelle. Un moteur maison couvre bien un catalogue de quelques centaines de variantes avec des règles simples. Au-delà, le coût de maintenance rejoint vite celui de la licence.
Package unlocked ou managed pour un outil interne Salesforce ?
Unlocked, dans un contexte de conseil mono-client. Il conserve le versionnage, l'URL d'installation et les mises à jour propres, sans namespace ni API verrouillée à vie. Et lui seul peut adopter des métadonnées déjà présentes dans l'org du client au lieu d'échouer sur une collision de nom.
Peut-on garder les devis existants quand on installe un nouvel outil de devis ?
Oui, si le nouvel outil est conçu pour absorber le modèle existant plutôt que le remplacer. Sur notre projet, l'objet section et trois champs du client ont été adoptés par le package, avec 528 sections et 1 713 lignes historiques conservées, puis migrées par script vers les nouveaux champs.