Approach 2: Using Client-Side Scripting (JavaScript) - Recommended for Better Control and UX
Client-Side Scripting provides more granular control over the form behavior and allows you to execute logic precisely when the Sales Stage changes.
- Add JavaScript Web Resource:
- Go to Settings > Customizations > Customize the System.
- Click Web Resources.
- Click + New.
- Give it a descriptive Name (e.g.,
opportunity_salesstage_validation.js).
- Set the Type to Script (JScript).
- Click Text Editor and paste your JavaScript code (see example below).
- Save and Publish the Web Resource.
- Attach the Web Resource to the Opportunity Form:
- Go to Settings > Customizations > Customize the System.
- Expand Entities and select the Opportunity entity.
- Expand Forms and open the main Opportunity form(s).
- In the Form Editor, click Form Properties.
- In the "Form Libraries" section, click + Add.
- Lookup and select the Web Resource you created.
- Click Add.
- Go to the Events tab.
- In the "Event Handlers" section, click + Add.
- Set the Control to the Sales Stage field (the field that represents your overall sales stage).
- Set the Event to OnChange.
- Set the Handler to the function name in your JavaScript code that will handle the validation (e.g.,
validateSalesStage).
- Check the Enabled box.
- Optionally, check Pass execution context as first parameter.
- Click OK.
- Save and Publish the Form.
-
Example JavaScript Code (opportunity_salesstage_validation.js):
function validateSalesStage(executionContext) {
var formContext = executionContext.getFormContext();
var salesStageControl = formContext.getAttribute("salesstage"); // Replace "salesstage" with the actual schema name of your Sales Stage field
var currentStageValue = salesStageControl.getValue();
var previousStageValue = formContext.getAttribute("previousstage").getValue(); // Assuming you have a field to store the previous stage
// Define the stages and their associated Yes/No question fields
var stageQuestions = {
"Qualify": ["new_question1_qualify", "new_question2_qualify", "new_question3_qualify"], // Replace with your actual field schema names
"Develop": ["new_question1_develop", "new_question2_develop"],
"Propose": ["new_question1_propose", "new_question2_propose", "new_question3_propose", "new_question4_propose"],
// Add other stages and their question fields
};
// Function to check if all questions in a stage are "Yes"
function areAllYes(stageName) {
if (stageQuestions.hasOwnProperty(stageName)) {
var questions = stageQuestions[stageName];
for (var i = 0; i < questions.length; i++) {
var answer = formContext.getAttribute(questions[i]).getValue();
if (answer !== true) { // Assuming "Yes" is stored as true (boolean)
return false;
}
}
return true;
}
return true; // If no questions for the stage, allow progression
}
// Get the stage display name (for the error message)
var stageOptions = salesStageControl.getOptions();
var currentStageDisplayName = "";
for (var i = 0; i < stageOptions.length; i++) {
if (stageOptions[i].value === currentStageValue) {
currentStageDisplayName = stageOptions[i].text;
break;
}
}
// Logic to prevent moving to the next stage
if (previousStageValue && currentStageValue !== previousStageValue) {
var previousStageDisplayName = "";
for (var i = 0; i < stageOptions.length; i++) {
if (stageOptions[i].value === previousStageValue) {
previousStageDisplayName = stageOptions[i].text;
break;
}
}
if (!areAllYes(previousStageDisplayName)) {
Xrm.AlertDialog({
title: "Validation Error",
text: "Please ensure all questions in the '" + previousStageDisplayName + "' stage are answered with 'Yes' before moving to '" + currentStageDisplayName + "'."
}).then(function () {
salesStageControl.setValue(previousStageValue); // Revert to the previous stage
});
}
}
// Store the current stage value for the next OnChange event
formContext.getAttribute("previousstage").setValue(currentStageValue);
}
// Optional: Add an "OnLoad" event to initialize the "previousstage" field
function initializePreviousStage(executionContext) {
var formContext = executionContext.getFormContext();
var currentStageValue = formContext.getAttribute("salesstage").getValue();
formContext.getAttribute("previousstage").setValue(currentStageValue);
}
Explanation of the JavaScript Code:
validateSalesStage(executionContext): This function will be triggered when the Sales Stage field changes.
formContext: Gets the form context to interact with form elements.
salesStageControl: Gets the control for the Sales Stage field.
currentStageValue: Gets the currently selected Sales Stage value.
previousStageValue: Gets the value of a temporary field (you'll need to create this) to store the previous Sales Stage.
stageQuestions: An object that maps your Sales Stage display names (or values) to an array of the schema names of the "Yes/No" question fields for that stage. You need to replace the placeholder field names with your actual schema names.
areAllYes(stageName): This helper function checks if all the "Yes/No" fields associated with a given stage have a value of true (assuming "Yes" is stored as a boolean true). Adjust the answer !== true condition if your "Yes/No" fields use a different data type or value.
- Logic:
- It checks if there was a previous stage and if the current stage is different.
- It calls
areAllYes() for the previous stage.
- If not all questions in the previous stage were "Yes," it displays an error message using
Xrm.AlertDialog and then sets the Sales Stage back to the previousStageValue.
- Storing Previous Stage: The code assumes you have a hidden field on the form (schema name:
previousstage) to store the value of the Sales Stage before it was changed. You'll need to add this field to your form and potentially set its value on load.
initializePreviousStage(executionContext) (Optional): This function can be attached to the form's OnLoad event to initialize the previousstage field with the initial Sales Stage value.
- Logic:
- It checks if there was a previous stage and if the current stage is different.
- It calls
areAllYes() for the previous stage.
- If not all questions in the previous stage were "Yes," it displays an error message using
Xrm.AlertDialog and then sets the Sales Stage back to the previousStageValue.
- Storing Previous Stage: The code assumes you have a hidden field on the form (schema name:
previousstage) to store the value of the Sales Stage before it was changed. You'll need to add this field to your form and potentially set its value on load.
initializePreviousStage(executionContext) (Optional): This function can be attached to the form's OnLoad event to initialize the previousstage field with the initial Sales Stage value.
Cont.. (3/3)