This framework centralizes all Opportunity stage-based validation rules in a Custom Metadata Type (CMT), providing an efficient, scalable, and easily maintainable way to manage validation logic. The implementation will leverage Before-Save Flows for performance and utilize Apex to enforce these rules dynamically within the code.
While you could use this approach to handle ALL validations on an object, that may become difficult to manage. This approach is intended to be used to validate data entry based on a field universally used on every record of a particular object, like Opportunity Stage or Case Status. We'll use the Opportunity Stage as the example for the rest of this article, in other words: all validations that rely on Opportunity Stage will be consolidated into this framework.
Framework Overview
Goals
- Centralize stage-based validation rules in a Custom Metadata Table.
- Use a Before-Save Flow to trigger validation logic.
- Enforce validation errors directly in Apex, using
addError()
methods for dynamic error handling. - Support validation for multiple fields, error messages, and error locations.
Key Components
1. Custom Metadata Type: Opportunity_Validation_Rules__mdt
The CMT will store all stage-based validation rules. Each record in the table represents a validation rule.
Fields for Opportunity_Validation_Rules__mdt:
Field Name | Type | Description |
---|---|---|
Opportunity Stage | Picklist | The stage for which the validation rule applies (e.g., "Negotiation"). |
Required Fields | Text (255) | Comma-separated list of required fields (e.g., CloseDate, Amount). |
Validation Error | Text (255) | The error message to display if validation fails. |
Error Location | Picklist | Where to display the error (Top of Page or Field). |
Active | Checkbox | Whether the rule is active (for easier maintenance). |
2. Apex Class: OpportunityValidationService
The Apex class will handle dynamic validation logic by querying the CMT and enforcing the rules for the Opportunity using addError()
methods.
Key Responsibilities:
- Query all active validation rules for the current stage.
- Check if the required fields specified in the CMT are populated.
- Enforce validation errors dynamically using
addError()
.
Updated Apex Code:
public with sharing class OpportunityValidationService {
public static void validateOpportunity(List<Opportunity> opportunities) {
// Query active validation rules
Map<String, List<Opportunity_Validation_Rules__mdt>> stageToRulesMap = new Map<String, List<Opportunity_Validation_Rules__mdt>>();
for (Opportunity_Validation_Rules__mdt rule : [
SELECT Opportunity_Stage__c, Required_Fields__c, Validation_Error__c, Error_Location__c
FROM Opportunity_Validation_Rules__mdt
WHERE Active__c = true
]) {
if (!stageToRulesMap.containsKey(rule.Opportunity_Stage__c)) {
stageToRulesMap.put(rule.Opportunity_Stage__c, new List<Opportunity_Validation_Rules__mdt>());
}
stageToRulesMap.get(rule.Opportunity_Stage__c).add(rule);
}
// Validate each Opportunity
for (Opportunity opp : opportunities) {
List<Opportunity_Validation_Rules__mdt> rules = stageToRulesMap.get(opp.StageName);
if (rules == null) continue; // No rules for this stage
for (Opportunity_Validation_Rules__mdt rule : rules) {
List<String> requiredFields = rule.Required_Fields__c.split(',');
for (String field : requiredFields) {
field = field.trim();
if (String.isEmpty((String)opp.get(field))) {
if (rule.Error_Location__c == 'Field') {
opp.addError(field, rule.Validation_Error__c);
} else {
opp.addError(rule.Validation_Error__c);
}
}
}
}
}
}
}
3. Flow: Validate Opportunity Before Save
A Before-Save Flow will trigger whenever an Opportunity is created or updated.
Steps:
- Trigger Flow on Opportunity:
- Object: Opportunity
- Trigger: Before Save
- Call Apex:
- Invoke the
OpportunityValidationService.validateOpportunity
method.
- Invoke the
- Error Handling:
- Errors applied through
addError()
will automatically display in the UI.
- Errors applied through
4. Data in the Custom Metadata Table
Here’s how the data might look in Opportunity_Validation_Rules__mdt
:
Opportunity Stage | Required Fields | Validation Error | Error Location | Active |
Qualification | Budget__c | Budget is required. | Field | True |
Proposal | Estimated_Launch_Date__c | Estimated Launch Date is required. | Top of Page | True |
Negotiation | Discount_Percentage__c | Discount must be specified. | Field | True |
Advantages of This Framework
- Centralized Management:
- All stage-based validations are managed in a single table, making it easier to maintain and update rules.
- Flexibility:
- Validation rules can be updated without modifying Flows or Apex. Changes to the CMT are instantly reflected in the logic.
- Reusability:
- The Apex service can handle any Opportunity validation logic tied to stages, reducing duplication.
- Scalability:
- Adding new rules for stages or fields requires only new records in the CMT.
- Performance:
- Using a Before-Save Flow ensures efficient processing by avoiding DML operations.
- Dynamic Errors for Multi-Field Validations:
- Able to be used to validate groups of fields required at the same time to streamline the user experience when hitting errors. The error message itself could also be made dynamic based on if some of a group of fields are missing while others are provided, to tailor error messages to a specific transaction.
Challenges to Consider
- Initial Setup Complexity:
- Building the CMT, Apex, and Flow requires upfront effort and planning.
- UX/UI:
- Clear handling of field-specific vs. page-level errors is essential for user understanding.
Conclusion
This framework is a scalable, maintainable, and powerful solution for managing Opportunity stage-based validations. By centralizing rules in Custom Metadata Types and using Apex to dynamically enforce validations, you ensure data quality and process flexibility.
This methodology is ideal for Salesforce architects and admins seeking to streamline complex validation requirements while maintaining scalability and simplicity.
Comments