UNPKG

sf-agent-framework

Version:

AI Agent Orchestration Framework for Salesforce Development - Two-phase architecture with 70% context reduction

499 lines (397 loc) 14.5 kB
# Salesforce Validation Patterns ## Overview Validation patterns ensure data integrity and business rule compliance in Salesforce. This document provides comprehensive validation patterns for different scenarios, including field validation, cross-object validation, and complex business rules. ## Field-Level Validation Patterns ### Email Validation ```javascript // Basic email validation AND(NOT(ISBLANK(Email)), NOT(REGEX(Email, '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'))); Error: 'Please enter a valid email address (e.g., user@example.com)'; // Domain-specific email validation AND(NOT(ISBLANK(Email)), NOT(CONTAINS(Email, '@mycompany.com')), ISPICKVAL(Type, 'Employee')); Error: 'Employee email must use @mycompany.com domain'; ``` ### Phone Number Validation ```javascript // US Phone Number (10 digits) AND(NOT(ISBLANK(Phone)), NOT(REGEX(Phone, '^\\(?[0-9]{3}\\)?[-. ]?[0-9]{3}[-. ]?[0-9]{4}$'))); Error: 'Phone must be 10 digits (e.g., (555) 123-4567)'; // International phone with country code AND(NOT(ISBLANK(Phone)), LEFT(Phone, 1) != '+', Country != 'United States'); Error: 'International phone numbers must start with + and country code'; ``` ### URL Validation ```javascript // Website URL validation AND(NOT(ISBLANK(Website)), NOT(OR(BEGINS(Website, 'http://'), BEGINS(Website, 'https://')))); Error: 'Website must start with http:// or https://'; // LinkedIn URL validation AND(NOT(ISBLANK(LinkedIn_URL__c)), NOT(CONTAINS(LinkedIn_URL__c, 'linkedin.com/in/'))); Error: 'Please enter a valid LinkedIn profile URL'; ``` ### Postal Code Validation ```javascript // US Zip Code AND((Country = 'USA'), NOT(ISBLANK(PostalCode)), NOT(REGEX(PostalCode, '^[0-9]{5}(-[0-9]{4})?$'))); Error: 'US postal code must be 5 digits or 9 digits with hyphen (12345 or 12345-6789)'; // Canadian Postal Code AND((Country = 'Canada'), NOT(ISBLANK(PostalCode)), NOT(REGEX(PostalCode, '^[A-Z][0-9][A-Z] ?[0-9][A-Z][0-9]$'))); Error: 'Canadian postal code must be in format A1A 1A1'; ``` ## Date and Time Validation Patterns ### Date Range Validation ```javascript // Future date required CloseDate <= TODAY(); Error: 'Close date must be in the future'; // Date within fiscal year OR(Contract_Start_Date__c < DATE(YEAR(TODAY()), 1, 1), Contract_Start_Date__c > DATE(YEAR(TODAY()), 12, 31)); Error: 'Contract must start within current fiscal year'; // Business days calculation AND( NOT(ISBLANK(Due_Date__c)), Due_Date__c < TODAY() + CASE( MOD(TODAY() - DATE(1900, 1, 7), 7), 0, 3 /* Sunday */, 1, 3 /* Monday */, 2, 3 /* Tuesday */, 3, 5 /* Wednesday */, 4, 5 /* Thursday */, 5, 5 /* Friday */, 6, 4 /* Saturday */, 3 ) ); Error: 'Due date must be at least 3 business days from today'; ``` ### Time-Based Validation ```javascript // Business hours validation AND( NOT(ISBLANK(Appointment_Time__c)), OR(VALUE(LEFT(TEXT(Appointment_Time__c), 2)) < 8, VALUE(LEFT(TEXT(Appointment_Time__c), 2)) > 17) ); Error: 'Appointments must be scheduled between 8 AM and 5 PM'; // SLA validation AND(ISPICKVAL(Priority, 'High'), Response_Time__c > 4); Error: 'High priority cases must have response time <= 4 hours'; ``` ## Numeric Validation Patterns ### Range Validation ```javascript // Percentage range OR(Discount__c < 0, Discount__c > 50); Error: 'Discount must be between 0% and 50%'; // Minimum value with conditions AND(Amount < 10000, ISPICKVAL(Type, 'Enterprise')); Error: 'Enterprise deals must be at least $10,000'; // Quantity validation OR(Quantity__c <= 0, MOD(Quantity__c, 1) != 0); Error: 'Quantity must be a positive whole number'; ``` ### Financial Validation ```javascript // Credit limit validation AND(Credit_Limit__c > 0, Outstanding_Balance__c > Credit_Limit__c * 1.1); Error: 'Outstanding balance cannot exceed 110% of credit limit'; // Margin calculation AND(Cost__c > 0, Revenue__c > 0, (Revenue__c - Cost__c) / Revenue__c < 0.15); Error: 'Margin must be at least 15%'; ``` ## Text Field Validation Patterns ### Format Validation ```javascript // SKU format validation AND(NOT(ISBLANK(Product_Code__c)), NOT(REGEX(Product_Code__c, '^[A-Z]{3}-[0-9]{4}-[A-Z0-9]{2}$'))); Error: 'Product code must be in format: ABC-1234-X1'; // No special characters AND(NOT(ISBLANK(Account_Code__c)), REGEX(Account_Code__c, '[^A-Za-z0-9]')); Error: 'Account code can only contain letters and numbers'; ``` ### Length Validation ```javascript // Minimum length LEN(Description) < 50; Error: 'Description must be at least 50 characters'; // Maximum length with rich text LEN(SUBSTITUTE(SUBSTITUTE(Rich_Text_Field__c, '<[^>]+>', ''), '&[^;]+;', '')) > 1000; Error: 'Content exceeds 1000 characters (excluding formatting)'; ``` ## Cross-Object Validation Patterns ### Parent-Child Validation ```javascript // Opportunity amount vs account credit limit AND(NOT(ISBLANK(AccountId)), Amount > Account.Credit_Limit__c); Error: 'Opportunity amount exceeds account credit limit'; // Contact email domain matches account AND( NOT(ISBLANK(Email)), NOT(ISBLANK(AccountId)), NOT(CONTAINS(Email, RIGHT(Account.Website, LEN(Account.Website) - FIND('//', Account.Website) - 1))) ); Error: 'Contact email domain should match account website domain'; ``` ### Related Record Validation ```javascript // Case priority based on account tier AND(ISPICKVAL(Account.Type, 'Enterprise'), ISPICKVAL(Priority, 'Low')); Error: 'Enterprise account cases cannot have Low priority'; // Opportunity stage vs activities AND(ISPICKVAL(StageName, 'Proposal/Price Quote'), Last_Activity_Date__c < TODAY() - 7); Error: 'Opportunities in Proposal stage require weekly activities'; ``` ## Business Rule Validation Patterns ### Status Progression ```javascript // Prevent backward status movement AND( ISCHANGED(Status__c), CASE( PRIORVALUE(Status__c), 'New', NOT(ISPICKVAL(Status__c, 'In Progress')), 'In Progress', NOT(OR(ISPICKVAL(Status__c, 'Completed'), ISPICKVAL(Status__c, 'Cancelled'))), 'Completed', true, 'Cancelled', true, false ) ); Error: 'Invalid status progression. Status can only move forward.'; ``` ### Conditional Requirements ```javascript // Required fields based on stage AND(ISPICKVAL(StageName, 'Closed Won'), OR(ISBLANK(Close_Reason__c), ISBLANK(Competitor__c), ISBLANK(Next_Steps__c))); Error: 'Close Reason, Competitor, and Next Steps required for Closed Won'; // Approval requirements AND(Discount__c > 20, ISBLANK(Approval_Date__c), NOT(($User.Id = Manager__c))); Error: 'Discounts over 20% require manager approval'; ``` ## Complex Validation Patterns ### Multi-Field Dependencies ```javascript // Address validation AND( OR(NOT(ISBLANK(Street)), NOT(ISBLANK(City)), NOT(ISBLANK(State)), NOT(ISBLANK(PostalCode))), OR(ISBLANK(Street), ISBLANK(City), ISBLANK(State), ISBLANK(PostalCode)) ); Error: 'If entering address, all fields (Street, City, State, Postal Code) are required'; ``` ### Calculation Validation ```javascript // Line item totals ABS(Quantity__c * Unit_Price__c * (1 - Discount__c / 100) - Total__c) > 0.01; Error: 'Total must equal Quantity × Unit Price × (1 - Discount%)'; // Percentage allocation AND( NOT(ISBLANK(Allocation_1__c)), NOT(ISBLANK(Allocation_2__c)), NOT(ISBLANK(Allocation_3__c)), Allocation_1__c + Allocation_2__c + Allocation_3__c != 100 ); Error: 'Allocations must total 100%'; ``` ## Apex Trigger Validation Patterns ### Complex Business Logic ```apex public class ValidationHelper { public static void validateComplexRules(List<Opportunity> opps) { // Collect related data Set<Id> accountIds = new Set<Id>(); for (Opportunity opp : opps) { accountIds.add(opp.AccountId); } Map<Id, Account> accounts = new Map<Id, Account>([ SELECT Id, Annual_Revenue__c, Industry, Number_of_Employees__c FROM Account WHERE Id IN :accountIds ]); // Apply validation for (Opportunity opp : opps) { Account acc = accounts.get(opp.AccountId); // Complex validation logic if (acc != null && acc.Industry == 'Technology') { if (opp.Amount < acc.Annual_Revenue__c * 0.1) { opp.addError('Technology deals must be at least 10% of annual revenue'); } } } } } ``` ### Duplicate Prevention ```apex trigger PreventDuplicates on Lead (before insert, before update) { Set<String> emails = new Set<String>(); for (Lead l : Trigger.new) { if (String.isNotBlank(l.Email)) { emails.add(l.Email.toLowerCase()); } } Map<String, Lead> existingLeads = new Map<String, Lead>(); for (Lead l : [SELECT Id, Email FROM Lead WHERE Email IN :emails]) { existingLeads.put(l.Email.toLowerCase(), l); } for (Lead l : Trigger.new) { if (String.isNotBlank(l.Email)) { Lead existing = existingLeads.get(l.Email.toLowerCase()); if (existing != null && existing.Id != l.Id) { l.addError('A lead with this email already exists: ' + existing.Id); } } } } ``` ## User and Profile Validation ### Role-Based Validation ```javascript // Only managers can approve AND(ISCHANGED(Approved__c), (Approved__c = true), $User.Id != Manager__c); Error: 'Only the assigned manager can approve this record'; // Profile-specific validation AND($Profile.Name != 'System Administrator', ISCHANGED(Locked_Field__c)); Error: 'Only System Administrators can modify this field'; ``` ### Time-Based User Validation ```javascript // Prevent edits after period close AND( $Profile.Name != "Finance Manager", PRIORVALUE(Period_Closed__c) = true, OR( ISCHANGED(Amount__c), ISCHANGED(Close_Date__c) ) ) Error: "Cannot modify financial fields after period close" ``` ## Integration Validation Patterns ### External System Validation ```apex // Validate against external system public class ExternalSystemValidator { @future(callout=true) public static void validateWithExternalSystem(Set<Id> recordIds) { List<Account> accounts = [SELECT Id, External_ID__c FROM Account WHERE Id IN :recordIds]; for (Account acc : accounts) { Http http = new Http(); HttpRequest request = new HttpRequest(); request.setEndpoint('https://api.external.com/validate/' + acc.External_ID__c); request.setMethod('GET'); HttpResponse response = http.send(request); if (response.getStatusCode() != 200) { // Log validation failure acc.Validation_Status__c = 'Failed'; acc.Validation_Message__c = response.getBody(); } } update accounts; } } ``` ## Validation Testing Patterns ### Unit Test Validation ```apex @isTest private class ValidationTest { @isTest static void testEmailValidation() { Contact c = new Contact( LastName = 'Test', Email = 'invalid-email' ); Test.startTest(); Database.SaveResult result = Database.insert(c, false); Test.stopTest(); System.assert(!result.isSuccess()); System.assert(result.getErrors()[0].getMessage().contains('valid email')); } @isTest static void testComplexValidation() { // Test multiple validation scenarios List<Opportunity> opps = new List<Opportunity>(); // Scenario 1: Valid opportunity opps.add(new Opportunity( Name = 'Valid Opp', StageName = 'Prospecting', CloseDate = Date.today().addDays(30), Amount = 50000 )); // Scenario 2: Invalid amount opps.add(new Opportunity( Name = 'Invalid Opp', StageName = 'Closed Won', CloseDate = Date.today().addDays(30), Amount = -1000 )); List<Database.SaveResult> results = Database.insert(opps, false); System.assert(results[0].isSuccess()); System.assert(!results[1].isSuccess()); } } ``` ## Performance Considerations ### Efficient Validation Rules ```javascript // Avoid multiple VLOOKUP calls // Bad: AND( VLOOKUP($ObjectType.User.Fields.Profile.Name, $User.Id) != 'Admin', VLOOKUP($ObjectType.User.Fields.Department, $User.Id) != 'Finance' ); // Good: AND($Profile.Name != 'Admin', $User.Department != 'Finance'); ``` ### Bulkified Trigger Validation ```apex // Efficient bulk validation public static void validateInBulk(List<SObject> records) { // Collect all needed data in one query Set<Id> relatedIds = new Set<Id>(); for (SObject record : records) { relatedIds.add((Id)record.get('RelatedId__c')); } // Single query for all related data Map<Id, RelatedObject__c> relatedMap = new Map<Id, RelatedObject__c>([ SELECT Id, Status__c, Type__c FROM RelatedObject__c WHERE Id IN :relatedIds ]); // Validate using cached data for (SObject record : records) { RelatedObject__c related = relatedMap.get((Id)record.get('RelatedId__c')); if (related != null && related.Status__c == 'Inactive') { record.addError('Cannot link to inactive record'); } } } ``` ## Best Practices Summary 1. **User-Friendly Messages**: Provide clear, actionable error messages 2. **Performance**: Minimize complex formulas and cross-object references 3. **Maintainability**: Document validation rules and their business purpose 4. **Testing**: Thoroughly test all validation scenarios 5. **Bypass Options**: Consider bypass mechanisms for data loads 6. **Order of Execution**: Understand validation rule timing 7. **Bulk Safety**: Ensure trigger validations handle bulk operations 8. **Version Control**: Track validation rule changes ## Additional Resources - [Validation Rule Examples](https://help.salesforce.com/s/articleView?id=sf.fields_validation_rules_examples.htm) - [Formula Function Reference](https://help.salesforce.com/s/articleView?id=sf.customize_functions.htm) - [Validation Best Practices](https://developer.salesforce.com/docs/atlas.en-us.usefulValidationFormulas.meta/usefulValidationFormulas/) - [Trigger Best Practices](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers_bestpract.htm)