mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-07-30 03:09:50 +08:00
202 lines
8.0 KiB
OpenEdge ABL
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 {}
|
|
}
|