Mettre à jour des options sans casser l’historique
Le directeur commercial arrive avec une liste d'options à faire évoluer. On arrête les adhésifs. On ajoute les abrasifs. Tu mets à jour l'option set dans Dataverse, tu ajustes le JavaScript, tu testes sur une fiche vierge. Tout s'affiche correctement. Tu pousses en prod.
Le lendemain, une commerciale te signale que son opportunité a le champ Déclinaison vide. La valeur était là ce matin.
La donnée est toujours en base. L'interface ne sait plus l'afficher.
Ce qui se passe avec clearOptions lors d'une transition de catalogue
Sur une configuration Dynamics 365 Sales où un champ Produit pilote un champ Déclinaison, le pattern classique ressemble à ça.
declinaisonControl.clearOptions();
declinaisonControl.addOption(optA);
declinaisonControl.addOption(optB);
clearOptions() vide tout le control. On reconstruit ensuite avec les valeurs actives selon le produit sélectionné.
Quand tu passes en N+1, tu n'ajoutes plus les déclinaisons des adhésifs. Une opportunité existante qui avait Adhésif Souple en déclinaison arrive sur le formulaire. clearOptions() s'exécute. Personne ne réinjecte la valeur. Le champ s'affiche vide.
La valeur existe toujours en base. Elle est simplement devenue invisible dans l'interface. La transition a cassé l'affichage, pas les données.
La clé : getAttribute().getOption() vs getControl().getOption()
getControl("p365_declinaison").getOption(value) lit les options actuellement dans le dropdown. Après clearOptions(), ce dictionnaire est vide. Aucun retour possible.
getAttribute("p365_declinaison").getOption(value) lit depuis les métadonnées de l'attribut dans Dataverse. Ces métadonnées ne sont pas affectées par clearOptions(). Elles contiennent toutes les valeurs définies sur l'option set, y compris les dépréciées.
⚠️ attr.getOption(value) retourne null si la valeur a été supprimée de la définition de l'option set dans Dataverse. En transition N/N+1, tant que tu ne supprimes pas la valeur de Dataverse, elle reste lisible via les métadonnées.
Le helper _preserveLegacyOptionIfSet
Trois étapes. Lire la valeur actuelle. Vérifier si elle fait partie des nouvelles options actives. Si non, la relire depuis les métadonnées et la réinjecter dans le control.
/**
* Réinjecte dans le control la valeur enregistrée sur la fiche
* si elle ne fait pas partie des nouvelles valeurs actives.
*
* Permet de gérer les transitions N/N+1 sans migration forcée :
* les fiches existantes gardent leur valeur visible,
* les nouvelles fiches n'affichent que les options en cours.
*
* @param {object} formContext
* @param {string} controlName - Nom du control option set
* @param {string} attributeName - Nom de l'attribut option set
* @param {number[]} activeValues - Valeurs actives dans la version en cours
*/
function _preserveLegacyOptionIfSet(formContext, controlName, attributeName, activeValues) {
var attr = formContext.getAttribute(attributeName);
if (attr == null) return;
var currentValue = attr.getValue();
if (currentValue == null) return;
if (activeValues.indexOf(currentValue) === -1) {
// Lecture depuis les métadonnées Dataverse — résistant à clearOptions()
var legacyOption = attr.getOption(currentValue);
if (legacyOption != null) {
formContext.getControl(controlName).addOption(legacyOption);
}
}
}
La fonction complète : du catalogue adhésifs/colle au catalogue colle/abrasifs
Catalogue N : Colle (déclinaisons Standard et Pro) + Adhésifs (déclinaisons Souple et Rigide).
Catalogue N+1 : Colle inchangée + Abrasifs remplace les Adhésifs (déclinaisons Grain Fin et Grain Gros).
Les opportunités en cours qui avaient Adhésifs en produit avec une déclinaison enregistrée doivent continuer à l'afficher. Gérer ça via l'administration Dynamics 365 sans forcer une mise à jour de toutes les fiches en prod.
function onLoadOnChange_opportunity_selectDeclinaisonProductValues(executionContext) {
'use strict';
var formContext = executionContext.getFormContext();
if (formContext.getAttribute("p365_declinaison") == null
|| formContext.getControl("p365_produit") == null) return;
var declinaisonAttr = formContext.getAttribute("p365_declinaison");
var declinaisonControl = formContext.getControl("p365_declinaison");
// Lecture depuis les métadonnées — getAttribute().getOption()
// Toujours disponible, même après clearOptions()
var optColleStandard = declinaisonAttr.getOption(100);
var optCollePro = declinaisonAttr.getOption(101);
var optAbrasifFin = declinaisonAttr.getOption(300);
var optAbrasifGros = declinaisonAttr.getOption(301);
// Les déclinaisons Adhésifs (200, 201) ne sont plus déclarées ici.
// _preserveLegacyOptionIfSet les réinjecte si la fiche les a en base.
var produitValue = formContext.getAttribute("p365_produit").getValue();
// Colle — actif N et N+1
if (produitValue === 1) {
declinaisonControl.clearOptions();
declinaisonControl.addOption(optColleStandard);
declinaisonControl.addOption(optCollePro);
_preserveLegacyOptionIfSet(formContext, "p365_declinaison", "p365_declinaison", [100, 101]);
}
// Abrasifs — nouveau en N+1
if (produitValue === 3) {
declinaisonControl.clearOptions();
declinaisonControl.addOption(optAbrasifFin);
declinaisonControl.addOption(optAbrasifGros);
_preserveLegacyOptionIfSet(formContext, "p365_declinaison", "p365_declinaison", [300, 301]);
}
// Adhésifs — déprécié en N+1
// Plus de nouvelles déclinaisons actives.
// On vide le control et on laisse le helper réinjecter la valeur si elle existe en base.
if (produitValue === 2) {
declinaisonControl.clearOptions();
_preserveLegacyOptionIfSet(formContext, "p365_declinaison", "p365_declinaison", []);
}
}
Le bloc Adhésifs avec activeValues = [] est le cas limite intéressant. Aucune nouvelle valeur n'est active. Le helper réinjecte systématiquement la valeur enregistrée, quelle qu'elle soit. L'utilisateur voit sa déclinaison, peut la garder ou basculer sur un autre produit.
Ce que l'utilisateur voit en production
| Scénario | Avant le fix | Après le fix |
|---|---|---|
| Opportunité existante, déclinaison Adhésif Souple | Champ Déclinaison vide à l'ouverture | Adhésif Souple affiché, valeur lisible |
| Nouvelle opportunité, produit Abrasifs | Grain Fin et Grain Gros affichés | Grain Fin et Grain Gros affichés (inchangé) |
| Modification d'une fiche existante Adhésifs | Champ vide, sauvegarde incohérente possible | Déclinaison visible, l'utilisateur peut la garder ou changer de produit |
Pas de migration en masse. Pas de script bulk update. Quand toutes les fiches actives seront passées sur des valeurs N+1, tu retires les appels à _preserveLegacyOptionIfSet. Les valeurs dépréciées disparaissent du dropdown sans toucher à la base.
Questions fréquentes
-
clearOptionssupprime-t-il les valeurs dans Dataverse ?clearOptionsest une méthode du control côté client. Elle vide le dropdown pour la session en cours uniquement. Les valeurs enregistrées sur les enregistrements restent intactes dans Dataverse. Pour modifier les données en base, il faut passer par l'API, un workflow, ou un outil comme XrmToolBox. -
Faut-il migrer les données en base lors d'une évolution de catalogue ?
Pas obligatoirement. Ce pattern permet de gérer la transition sans migration forcée : les enregistrements existants gardent leur valeur visible, les nouvelles fiches n'accèdent qu'aux options en cours. La migration peut se planifier sereinement, sans bloquer la mise en prod du nouveau catalogue.
-
Quelle est la différence entre
getAttribute().getOption()etgetControl().getOption()?getControl().getOption()lit les options actuellement dans le dropdown. Vide aprèsclearOptions().getAttribute().getOption()lit depuis les métadonnées Dataverse, toujours disponibles quelle que soit l'état du control. C'est cette différence qui rend le helper possible. -
Si l'utilisateur enregistre sans toucher au champ, la valeur dépréciée est-elle conservée ?
Oui. Le helper réinjecte la valeur dans le control à chaque chargement. Si l'utilisateur n'y touche pas et enregistre, Dataverse conserve la valeur inchangée. Elle disparaîtra uniquement si l'utilisateur la remplace lui-même par une valeur N+1.
-
Cette technique fonctionne-t-elle sur un multi-select option set ?
Pas directement. Sur un MultiSelectPicklist,
getValue()retourne un tableau de nombres. La logique doit être adaptée pour vérifier chaque valeur dépréciée dans le tableau et les réinjecter individuellement dans le control.
Tu gères un CRM Dynamics 365 qui évolue ?
Scripts JS, évolutions de formulaires, transitions de données en production.
Je prends en charge l'administration technique pour que ton CRM suive le rythme de ton catalogue.