/** * @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> mergeMap = new Map>{ * masterAcctId => new Set{ dupeId1, dupeId2 } * }; * List mergedIds = AccountService.mergeAccounts(mergeMap); */ public static List mergeAccounts(Map> mergeMap) { if (mergeMap == null || mergeMap.isEmpty()) { throw new AccountServiceException(ERROR_NULL_INPUT); } // Collect all Ids for a single query Set allIds = new Set(); allIds.addAll(mergeMap.keySet()); for (Set dupeIds : mergeMap.values()) { allIds.addAll(dupeIds); } // Query all records at once via Selector Map accountMap = AccountSelector.selectMapByIds(allIds); List successfulMerges = new List(); List errors = new List(); for (Id masterId : mergeMap.keySet()) { Account master = accountMap.get(masterId); if (master == null) { errors.add('Master account not found: ' + masterId); continue; } List duplicates = new List(); 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 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 accountIds) { if (accountIds == null || accountIds.isEmpty()) { throw new AccountServiceException(ERROR_NULL_INPUT); } List accounts = AccountSelector.selectWithBillingAddress(accountIds); Map territoryMap = loadTerritoryMappings(); List toUpdate = new List(); 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 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{ accountId }); } // ─── Private Helpers ───────────────────────────────────────────────── /** * @description Loads territory mappings from Custom Metadata * @return Map of territory key (State:Country) to territory name */ private static Map loadTerritoryMappings() { Map mappings = new Map(); 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> chunkAccounts(List accounts, Integer chunkSize) { List> chunks = new List>(); List current = new List(); for (Account acct : accounts) { current.add(acct); if (current.size() == chunkSize) { chunks.add(current); current = new List(); } } 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 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 {} }