afv-library/skills/apex-development/apex-class/examples/AccountService.cls
2026-02-10 11:57:55 -05:00

202 lines
8.0 KiB
OpenEdge ABL

/**
* @description Service class for Account business logic.
* Provides account deduplication, enrichment, and territory assignment.
* Delegates queries to AccountSelector and SObject manipulation to AccountDomain.
* @author Generated by Apex Class Writer Skill
*/
public with sharing class AccountService {
// Constants
private static final String ERROR_NULL_INPUT = 'Input cannot be null or empty.';
private static final String ERROR_MERGE_FAILED = 'Account merge failed for master Id: ';
private static final Integer MAX_MERGE_BATCH = 3;
// Public API
/**
* @description Merges duplicate Account records into a master record.
* The master record retains its field values; child records are reparented.
* @param masterIds Map of master Account Id to Set of duplicate Account Ids to merge
* @return List of master Account Ids that were successfully merged
* @throws AccountServiceException if merge processing fails
* @example
* Map<Id, Set<Id>> mergeMap = new Map<Id, Set<Id>>{
* masterAcctId => new Set<Id>{ dupeId1, dupeId2 }
* };
* List<Id> mergedIds = AccountService.mergeAccounts(mergeMap);
*/
public static List<Id> mergeAccounts(Map<Id, Set<Id>> mergeMap) {
if (mergeMap == null || mergeMap.isEmpty()) {
throw new AccountServiceException(ERROR_NULL_INPUT);
}
// Collect all Ids for a single query
Set<Id> allIds = new Set<Id>();
allIds.addAll(mergeMap.keySet());
for (Set<Id> dupeIds : mergeMap.values()) {
allIds.addAll(dupeIds);
}
// Query all records at once via Selector
Map<Id, Account> accountMap = AccountSelector.selectMapByIds(allIds);
List<Id> successfulMerges = new List<Id>();
List<String> errors = new List<String>();
for (Id masterId : mergeMap.keySet()) {
Account master = accountMap.get(masterId);
if (master == null) {
errors.add('Master account not found: ' + masterId);
continue;
}
List<Account> duplicates = new List<Account>();
for (Id dupeId : mergeMap.get(masterId)) {
Account dupe = accountMap.get(dupeId);
if (dupe != null) {
duplicates.add(dupe);
}
}
try {
// Apex merge supports up to 3 records at a time
for (List<Account> chunk : chunkAccounts(duplicates, MAX_MERGE_BATCH)) {
Database.merge(master, chunk);
}
successfulMerges.add(masterId);
} catch (DmlException e) {
errors.add(ERROR_MERGE_FAILED + masterId + ' - ' + e.getMessage());
}
}
if (!errors.isEmpty()) {
System.debug(LoggingLevel.WARN, 'Merge errors: ' + String.join(errors, '\n'));
}
return successfulMerges;
}
/**
* @description Assigns accounts to territories based on Billing State/Country.
* Uses Custom Metadata Type (Territory_Mapping__mdt) for mappings.
* @param accountIds Set of Account Ids to assign territories for
* @return Number of accounts successfully updated
* @throws AccountServiceException if territory assignment fails
*/
public static Integer assignTerritories(Set<Id> accountIds) {
if (accountIds == null || accountIds.isEmpty()) {
throw new AccountServiceException(ERROR_NULL_INPUT);
}
List<Account> accounts = AccountSelector.selectWithBillingAddress(accountIds);
Map<String, String> territoryMap = loadTerritoryMappings();
List<Account> toUpdate = new List<Account>();
for (Account acct : accounts) {
String key = buildTerritoryKey(acct.BillingState, acct.BillingCountry);
String territory = territoryMap.get(key);
if (territory != null && territory != acct.Territory__c) {
toUpdate.add(new Account(
Id = acct.Id,
Territory__c = territory
));
}
}
if (!toUpdate.isEmpty()) {
List<Database.SaveResult> results = Database.update(toUpdate, false);
return countSuccesses(results);
}
return 0;
}
// Convenience Overloads
/**
* @description Single-account territory assignment convenience method
* @param accountId The Account Id to assign a territory for
* @return 1 if updated, 0 if no change needed
*/
public static Integer assignTerritory(Id accountId) {
return assignTerritories(new Set<Id>{ accountId });
}
// Private Helpers
/**
* @description Loads territory mappings from Custom Metadata
* @return Map of territory key (State:Country) to territory name
*/
private static Map<String, String> loadTerritoryMappings() {
Map<String, String> mappings = new Map<String, String>();
for (Territory_Mapping__mdt mapping : Territory_Mapping__mdt.getAll().values()) {
String key = buildTerritoryKey(mapping.State__c, mapping.Country__c);
mappings.put(key, mapping.Territory_Name__c);
}
return mappings;
}
/**
* @description Builds a consistent territory lookup key
* @param state The billing state
* @param country The billing country
* @return A normalized key string
*/
private static String buildTerritoryKey(String state, String country) {
return (state ?? '').toUpperCase() + ':' + (country ?? '').toUpperCase();
}
/**
* @description Chunks a list of Accounts into sublists of the given size
* @param accounts The accounts to chunk
* @param chunkSize Maximum chunk size
* @return List of account sublists
*/
private static List<List<Account>> chunkAccounts(List<Account> accounts, Integer chunkSize) {
List<List<Account>> chunks = new List<List<Account>>();
List<Account> current = new List<Account>();
for (Account acct : accounts) {
current.add(acct);
if (current.size() == chunkSize) {
chunks.add(current);
current = new List<Account>();
}
}
if (!current.isEmpty()) {
chunks.add(current);
}
return chunks;
}
/**
* @description Counts successful results from a DML operation
* @param results List of Database.SaveResult
* @return Count of successful operations
*/
private static Integer countSuccesses(List<Database.SaveResult> results) {
Integer count = 0;
for (Database.SaveResult sr : results) {
if (sr.isSuccess()) {
count++;
} else {
for (Database.Error err : sr.getErrors()) {
System.debug(LoggingLevel.WARN,
'Update failed for ' + sr.getId() + ': ' + err.getMessage()
);
}
}
}
return count;
}
// Exception
/**
* @description Custom exception for AccountService errors
*/
public class AccountServiceException extends Exception {}
}