UNPKG

sf-agent-framework

Version:

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

666 lines (525 loc) 18.1 kB
# Common Salesforce Data Transformations ## Overview Data transformations are critical when importing, exporting, or synchronizing data with Salesforce. This document covers common transformation patterns, formulas, and best practices for data manipulation. ## String Transformations ### Case Transformations ```apex // Proper case transformation public static String toProperCase(String input) { if (String.isBlank(input)) return input; List<String> words = input.toLowerCase().split(' '); List<String> result = new List<String>(); for (String word : words) { if (word.length() > 0) { result.add(word.substring(0, 1).toUpperCase() + word.substring(1)); } } return String.join(result, ' '); } // Title case with exceptions public static String toTitleCase(String input) { Set<String> exceptions = new Set<String>{ 'a', 'an', 'and', 'as', 'at', 'but', 'by', 'for', 'if', 'in', 'nor', 'of', 'on', 'or', 'so', 'the', 'to', 'up', 'yet' }; List<String> words = input.toLowerCase().split(' '); List<String> result = new List<String>(); for (Integer i = 0; i < words.size(); i++) { String word = words[i]; if (i == 0 || i == words.size() - 1 || !exceptions.contains(word)) { word = word.substring(0, 1).toUpperCase() + word.substring(1); } result.add(word); } return String.join(result, ' '); } ``` ### Text Cleaning ```apex // Remove special characters public static String cleanText(String input) { // Remove non-printable characters String cleaned = input.replaceAll('[\x00-\x1F\x7F]', ''); // Normalize whitespace cleaned = cleaned.replaceAll('\s+', ' '); // Trim return cleaned.trim(); } // Remove HTML tags public static String stripHtml(String input) { return input.replaceAll('<[^>]+>', ''); } // Truncate with ellipsis public static String truncate(String input, Integer maxLength) { if (input == null || input.length() <= maxLength) { return input; } return input.substring(0, maxLength - 3) + '...'; } ``` ### String Parsing ```apex // Extract email domain public static String extractDomain(String email) { if (email != null && email.contains('@')) { return email.substring(email.indexOf('@') + 1).toLowerCase(); } return null; } // Parse full name public static Map<String, String> parseName(String fullName) { Map<String, String> result = new Map<String, String>(); if (String.isBlank(fullName)) { return result; } List<String> parts = fullName.trim().split('\\s+'); if (parts.size() >= 1) { result.put('firstName', parts[0]); } if (parts.size() >= 2) { result.put('lastName', parts[parts.size() - 1]); } if (parts.size() >= 3) { List<String> middleParts = new List<String>(); for (Integer i = 1; i < parts.size() - 1; i++) { middleParts.add(parts[i]); } result.put('middleName', String.join(middleParts, ' ')); } return result; } ``` ## Number Transformations ### Formatting Numbers ```apex // Format currency public static String formatCurrency(Decimal amount, String currencyCode) { if (amount == null) return ''; Map<String, String> symbols = new Map<String, String>{ 'USD' => '$', 'EUR' => '€', 'GBP' => '£', 'JPY' => '¥' }; String symbol = symbols.get(currencyCode) != null ? symbols.get(currencyCode) : currencyCode + ' '; // Format with thousands separator String formatted = amount.format(); return symbol + formatted; } // Format percentage public static String formatPercentage(Decimal value, Integer decimalPlaces) { if (value == null) return ''; Decimal percentage = value * 100; return percentage.setScale(decimalPlaces).format() + '%'; } // Round to nearest public static Decimal roundToNearest(Decimal value, Decimal nearest) { if (value == null || nearest == null || nearest == 0) return value; return Math.round(value / nearest) * nearest; } ``` ### Number Parsing ```apex // Parse currency string public static Decimal parseCurrency(String currencyString) { if (String.isBlank(currencyString)) return null; // Remove currency symbols and commas String cleaned = currencyString.replaceAll('[^0-9.-]', ''); try { return Decimal.valueOf(cleaned); } catch (Exception e) { return null; } } // Parse percentage public static Decimal parsePercentage(String percentString) { if (String.isBlank(percentString)) return null; String cleaned = percentString.replaceAll('[^0-9.-]', ''); try { return Decimal.valueOf(cleaned) / 100; } catch (Exception e) { return null; } } ``` ## Date and Time Transformations ### Date Formatting ```apex // Format date in various formats public static String formatDate(Date inputDate, String format) { if (inputDate == null) return ''; DateTime dt = DateTime.newInstance(inputDate.year(), inputDate.month(), inputDate.day()); switch on format { when 'ISO' { return dt.format('yyyy-MM-dd'); } when 'US' { return dt.format('MM/dd/yyyy'); } when 'EU' { return dt.format('dd/MM/yyyy'); } when 'LONG' { return dt.format('MMMM d, yyyy'); } when else { return dt.format(format); } } } // Convert between timezones public static DateTime convertTimezone(DateTime dt, String fromTz, String toTz) { // Get timezone offsets TimeZone fromTimeZone = TimeZone.getTimeZone(fromTz); TimeZone toTimeZone = TimeZone.getTimeZone(toTz); Integer fromOffset = fromTimeZone.getOffset(dt); Integer toOffset = toTimeZone.getOffset(dt); // Calculate difference in milliseconds Integer difference = toOffset - fromOffset; return dt.addSeconds(difference / 1000); } ``` ### Date Calculations ```apex // Business days calculation public static Integer getBusinessDaysBetween(Date startDate, Date endDate) { Integer businessDays = 0; Date currentDate = startDate; while (currentDate <= endDate) { DateTime dt = DateTime.newInstance(currentDate.year(), currentDate.month(), currentDate.day()); String dayOfWeek = dt.format('E'); if (dayOfWeek != 'Sat' && dayOfWeek != 'Sun') { businessDays++; } currentDate = currentDate.addDays(1); } return businessDays; } // Add business days public static Date addBusinessDays(Date startDate, Integer daysToAdd) { Date resultDate = startDate; Integer addedDays = 0; while (addedDays < daysToAdd) { resultDate = resultDate.addDays(1); DateTime dt = DateTime.newInstance(resultDate.year(), resultDate.month(), resultDate.day()); String dayOfWeek = dt.format('E'); if (dayOfWeek != 'Sat' && dayOfWeek != 'Sun') { addedDays++; } } return resultDate; } ``` ## Phone Number Transformations ### Phone Formatting ```apex public class PhoneFormatter { // Format US phone numbers public static String formatUSPhone(String phone) { if (String.isBlank(phone)) return phone; // Remove all non-numeric characters String cleaned = phone.replaceAll('[^0-9]', ''); // Handle different lengths if (cleaned.length() == 10) { return String.format('({0}) {1}-{2}', new List<String>{ cleaned.substring(0, 3), cleaned.substring(3, 6), cleaned.substring(6) }); } else if (cleaned.length() == 11 && cleaned.startsWith('1')) { return '+1 ' + formatUSPhone(cleaned.substring(1)); } else if (cleaned.length() == 7) { return String.format('{0}-{1}', new List<String>{ cleaned.substring(0, 3), cleaned.substring(3) }); } return phone; } // Format international phone numbers public static String formatInternational(String phone, String countryCode) { if (String.isBlank(phone)) return phone; Map<String, String> formats = new Map<String, String>{ 'GB' => '+44 {0} {1} {2}', // UK 'DE' => '+49 {0} {1}', // Germany 'FR' => '+33 {0} {1} {2} {3} {4}', // France 'AU' => '+61 {0} {1} {2} {3}' // Australia }; // Implementation varies by country return phone; // Simplified } } ``` ## Address Transformations ### Address Standardization ```apex public class AddressStandardizer { // Standardize US addresses public static Map<String, String> standardizeUSAddress(String fullAddress) { Map<String, String> result = new Map<String, String>(); // Common abbreviations Map<String, String> abbreviations = new Map<String, String>{ 'street' => 'St', 'avenue' => 'Ave', 'boulevard' => 'Blvd', 'drive' => 'Dr', 'lane' => 'Ln', 'road' => 'Rd', 'north' => 'N', 'south' => 'S', 'east' => 'E', 'west' => 'W' }; // Parse address components // This is simplified - real implementation would be more complex List<String> lines = fullAddress.split('\n'); if (lines.size() >= 1) { result.put('street', standardizeStreet(lines[0], abbreviations)); } if (lines.size() >= 2) { parseCity StateZip(lines[1], result); } return result; } // Parse city, state, zip private static void parseCityStateZip(String line, Map<String, String> result) { // Match pattern: City, ST 12345 Pattern p = Pattern.compile('([^,]+),\\s*([A-Z]{2})\\s+(\\d{5}(-\\d{4})?)$'); Matcher m = p.matcher(line); if (m.find()) { result.put('city', m.group(1).trim()); result.put('state', m.group(2)); result.put('postalCode', m.group(3)); } } } ``` ## Email Transformations ### Email Validation and Cleaning ```apex public class EmailTransformer { // Validate and clean email public static String cleanEmail(String email) { if (String.isBlank(email)) return null; // Trim and lowercase email = email.trim().toLowerCase(); // Remove common typos email = email.replace('..', '.'); email = email.replace('.@', '@'); email = email.replace('@.', '@'); // Validate format Pattern emailPattern = Pattern.compile( '^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$' ); if (emailPattern.matcher(email).matches()) { return email; } return null; } // Extract name from email public static Map<String, String> extractNameFromEmail(String email) { Map<String, String> result = new Map<String, String>(); if (email != null && email.contains('@')) { String localPart = email.substring(0, email.indexOf('@')); // Try common patterns if (localPart.contains('.')) { List<String> parts = localPart.split('\\.'); result.put('firstName', capitalize(parts[0])); result.put('lastName', capitalize(parts[parts.size() - 1])); } else if (localPart.contains('_')) { List<String> parts = localPart.split('_'); result.put('firstName', capitalize(parts[0])); result.put('lastName', capitalize(parts[parts.size() - 1])); } } return result; } } ``` ## Picklist Value Transformations ### Picklist Mapping ```apex public class PicklistMapper { // Map external values to Salesforce picklist values public static String mapToPicklist(String externalValue, String objectName, String fieldName) { // Get picklist values Schema.DescribeFieldResult fieldResult = Schema.getGlobalDescribe().get(objectName).getDescribe() .fields.getMap().get(fieldName).getDescribe(); List<Schema.PicklistEntry> picklistValues = fieldResult.getPicklistValues(); // Create mapping Map<String, String> valueMap = new Map<String, String>(); for (Schema.PicklistEntry pe : picklistValues) { valueMap.put(pe.getLabel().toLowerCase(), pe.getValue()); valueMap.put(pe.getValue().toLowerCase(), pe.getValue()); } // Try exact match first String normalized = externalValue.trim().toLowerCase(); if (valueMap.containsKey(normalized)) { return valueMap.get(normalized); } // Try fuzzy matching for (String key : valueMap.keySet()) { if (key.contains(normalized) || normalized.contains(key)) { return valueMap.get(key); } } return null; // No match found } } ``` ## Boolean Transformations ### Boolean Parsing ```apex public static Boolean parseBoolean(String value) { if (String.isBlank(value)) return null; Set<String> trueValues = new Set<String>{ 'true', 'yes', 'y', '1', 'on', 'active', 'enabled' }; Set<String> falseValues = new Set<String>{ 'false', 'no', 'n', '0', 'off', 'inactive', 'disabled' }; String normalized = value.trim().toLowerCase(); if (trueValues.contains(normalized)) { return true; } else if (falseValues.contains(normalized)) { return false; } return null; } ``` ## ID and Reference Transformations ### ID Conversions ```apex public class IdTransformer { // Convert 15-character ID to 18-character public static String to18CharId(String id15) { if (String.isBlank(id15) || id15.length() != 15) { return id15; } String suffix = ''; for (Integer i = 0; i < 3; i++) { Integer flags = 0; for (Integer j = 0; j < 5; j++) { String c = id15.substring(i * 5 + j, i * 5 + j + 1); if (c.isAllUpperCase() && c.isAlpha()) { flags += 1 << j; } } suffix += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345'.substring(flags, flags + 1); } return id15 + suffix; } // Create external ID from multiple fields public static String createCompositeKey(List<String> values) { List<String> cleaned = new List<String>(); for (String value : values) { if (!String.isBlank(value)) { cleaned.add(value.trim()); } } return String.join(cleaned, '_'); } } ``` ## Bulk Data Transformations ### Batch Transformation Framework ```apex public class BulkTransformer implements Database.Batchable<SObject> { private String query; private TransformationRule rule; public BulkTransformer(String query, TransformationRule rule) { this.query = query; this.rule = rule; } public Database.QueryLocator start(Database.BatchableContext bc) { return Database.getQueryLocator(query); } public void execute(Database.BatchableContext bc, List<SObject> scope) { for (SObject record : scope) { rule.transform(record); } update scope; } public void finish(Database.BatchableContext bc) { // Send completion notification } } // Transformation rule interface public interface TransformationRule { void transform(SObject record); } // Example implementation public class PhoneStandardizationRule implements TransformationRule { public void transform(SObject record) { String phone = (String)record.get('Phone'); if (!String.isBlank(phone)) { record.put('Phone', PhoneFormatter.formatUSPhone(phone)); } } } ``` ## Formula Field Transformations ### Common Formula Patterns ```sql -- Concatenate with null handling IF(ISBLANK(FirstName), LastName, FirstName & ' ' & LastName ) -- Extract domain from email IF(CONTAINS(Email, '@'), MID(Email, FIND('@', Email) + 1, LEN(Email)), null ) -- Calculate age from birthdate IF(ISBLANK(Birthdate), null, FLOOR((TODAY() - Birthdate) / 365.25) ) -- Format phone for display IF(LEN(Phone) = 10, '(' & LEFT(Phone, 3) & ') ' & MID(Phone, 4, 3) & '-' & RIGHT(Phone, 4), Phone ) -- Conditional picklist mapping CASE(Status__c, 'New', 'Not Started', 'In Progress', 'Working', 'Complete', 'Finished', 'Unknown' ) ``` ## Performance Considerations ### Optimization Strategies 1. **Batch Processing**: Use Database.Batchable for large datasets 2. **Bulkification**: Process records in collections 3. **Lazy Loading**: Transform only when needed 4. **Caching**: Store transformation results 5. **Async Processing**: Use @future or Queueable ### Memory Management ```apex // Process large datasets in chunks public static void transformLargeDataset(List<Id> recordIds) { Integer chunkSize = 200; for (Integer i = 0; i < recordIds.size(); i += chunkSize) { List<Id> chunk = new List<Id>(); for (Integer j = i; j < Math.min(i + chunkSize, recordIds.size()); j++) { chunk.add(recordIds[j]); } processChunk(chunk); } } @future private static void processChunk(List<Id> recordIds) { // Process chunk asynchronously } ``` ## Additional Resources - [Salesforce Formula Functions](https://help.salesforce.com/s/articleView?id=sf.customize_functions.htm) - [Apex String Methods](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_string.htm) - [Data Loader Transformation Guide](https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/) - [ETL Best Practices](https://developer.salesforce.com/docs/atlas.en-us.integration_patterns_and_practices.meta/)