Pricing·Avancé·11 min·Mis à jour le 05/10/2026
Guide · Pricing
Avenant négatif à 0 € : la ligne qui ne sait pas qu’elle a un prix
Retirer des licences par avenant produit une ligne à zéro, donc pas d’avoir. Trois semaines de fausses pistes, une branche morte dans la procédure standard, et l’étape Map Line Item qui manquait.
D’où vient chaque affirmation
Scroll the diagram sideways
La règle qui garde un catalogue maintenable : une decision table seulement s’il y a une condition ou un palier. Un prix fixe vit sur le PricebookEntry. Les decision tables ajoutées par réflexe sont ce qui remplit une procédure jusqu’à son plafond.
Et la procédure qui s’exécute est la version compilée _Auto, pas la source que vous éditez.
L’avenant positif, lui, marche parfaitement : +2 licences, montant au prorata, facture juste. C’est cette asymétrie qui met trois semaines à s’expliquer.
Le moteur connaissait le prix
Premier réflexe : le prix est perdu quelque part. Faux.
Le détail de ligne — l’enregistrement de détail rattaché à la ligne de devis, et son équivalent sur la commande — porte le bon prix négocié, la bonne quantité négative, et la référence à la vente d’origine. Seul son montant total vaut 0. Terrain
Le moteur n’a jamais manqué d’information. Il manquait une multiplication.
La branche morte
Dans la procédure standard, le groupe qui traite les lignes « prix de la dernière
transaction » est filtré sur un attribut ItemPricingSource égal à
LastTransaction. Cet attribut est alimenté par un champ de source de prix sur la
ligne de devis et de commande. Sur notre org en Summer ’26, ce champ n’existait que
sur l’actif. L’attribut était donc vide partout, et le groupe ne s’exécutait jamais.
Terrain
Les lignes normales passent par la branche opposée, « différent de LastTransaction », que la valeur vide satisfait. C’est exactement pour ça que l’avenant positif marchait et pas le négatif.
On a réanimé le filtre pour tester. La ligne est restée à 0 : la formule du groupe multipliait un prix net vide au niveau de la ligne. Le prix était sur le détail, pas sur la ligne. Ce n’était pas un filtre mal réglé, c’était une étape absente.
Les fausses pistes, et pourquoi elles échouent
- Un hook Apex dans le procedure plan. Pour poser le prix avant la procédure. Le hook écrit bien dans le contexte, mais la section pricing suivante relit les enregistrements et écrase tout. Terrain
- Poser le prix à la main sur la commande d’avenant. Le « Reprice All » l’efface, puis le verrouille : « You can’t edit the Unit Price because the Order Product has related Order Product Detail records ». Terrain
- Accuser le devis. La commande fait exactement pareil. Le problème est dans la procédure, pas dans l’objet d’entrée. Terrain
La réponse : Map Line Item
C’est le support Salesforce qui a donné la clé. Avec Advanced Transaction Detail Line Pricing activé, la procédure doit contenir l’élément Map Line Item, en deuxième position, juste après les réglages de pricing. Il fait le lien entre la ligne et ses détails. Sans lui, les valeurs ne circulent pas entre les deux niveaux. Doc
Une sandbox de démonstration, sans ce réglage avancé, n’avait pas le problème. C’est ce qui rendait le bug si difficile à reproduire — et ce qui explique qu’il ne soit documenté nulle part comme un symptôme.
Côté métadonnée, l’élément s’appelle BreakdownLineMapping : c’est une valeur du
type d’action d’une étape d’expression set, en API 64 et au-delà. Ses paramètres ne
sont documentés nulle part. On les a repris d’un dépôt public de référence : une
section par attribut, entrée vers sortie, de l’élément de transaction vers son détail,
dix-huit sections au total. Terrain
Résultat : l’avenant −1 sort au prorata, au bon prix, et l’échéancier négatif suit. Terrain
Le piège Winter ’27 qui a tout bloqué
En production, en Winter ’27, impossible de sauvegarder la moindre procédure contenant une étape de prix catalogue : « Ensure that the lookup table in the ListPrice step is valid ». Même une procédure neuve, générée par Salesforce. Terrain
La cause : la pricing recipe, créée avant Winter ’27, avait un sous-type d’usage vide. Winter ’27 valide les tables de lookup par rapport à une recette de sous-type Revenue Cloud. Le champ n’est pas modifiable sur une recette existante. Il faut créer une deuxième recette, avec les mêmes mappings et le bon sous-type. Terrain
Et deux réflexes qui évitent de perdre une journée :
- Une version active ne se modifie pas. On ajoute une version de rang supérieur ; la précédente reste en place pour revenir en arrière.
- Ne désactivez jamais la version en production avant que la nouvelle soit active. Une procédure sans version active, c’est tout le pricing à l’arrêt — et le symptôme, toutes les lignes au prix catalogue, ressemble au bug qu’on vient de corriger.
Ce qui reste ouvert
- Le détail reprend le prix catalogue, pas le prix négocié. Quand rien ne lui fournit la dernière transaction. Selon la politique d’avoir, ça se décide avec la finance, pas en configuration.
- La facturation d’un swap natif. Refuse les lignes de type SwapIn ou SwapOut dès qu’il y en a une dans l’appel. Filtrer ces lignes permet de facturer le nouveau bundle, pas de créditer l’ancien. Terrain
Le symptôme voisin — un renouvellement qui sort au prix catalogue — a une autre cause, et sa propre démonstration : voir Renouvellement au prix catalogue.
Composants Salesforce
Sélectionnez-en un pour ouvrir son détail.
Sur quoi ça repose
- Le problème n’apparaît qu’avec Advanced Transaction Detail Line Pricing activé. Vérifiez ce réglage avant toute chose : sans lui, ce n’est pas votre bug.
- Le champ de source de prix est apparu depuis sur notre org. Le comportement de la branche « dernière transaction » peut donc changer avec une release : à vérifier sur la vôtre.
- Les paramètres de
BreakdownLineMappingviennent d’un dépôt public, pas de la documentation officielle. Ils fonctionnent, ils ne sont pas garantis. - Constaté sur une org en Summer ’26 / Winter ’27.