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

149 lines
6.0 KiB
OpenEdge ABL

/**
* @description Batch Apex class for identifying and flagging duplicate Account records.
* Compares Accounts by Name and BillingPostalCode to find potential duplicates.
* Flags duplicates by setting the Is_Potential_Duplicate__c checkbox.
* Implements Database.Stateful to track results across batch chunks.
* @author Generated by Apex Class Writer Skill
*
* @example
* // Run with default batch size (200)
* Id jobId = AccountDeduplicationBatch.run();
*
* // Run with smaller batch size for complex processing
* Database.executeBatch(new AccountDeduplicationBatch(), 50);
*/
public with sharing class AccountDeduplicationBatch implements Database.Batchable<SObject>, Database.Stateful {
// Constants
private static final Integer DEFAULT_BATCH_SIZE = 200;
// Stateful Tracking
private Integer totalScanned = 0;
private Integer duplicatesFound = 0;
private Integer totalErrors = 0;
private List<String> errorMessages = new List<String>();
// Batchable Interface
/**
* @description Queries all active Accounts that haven't already been flagged
* @param bc The batch context
* @return QueryLocator scoped to unflagged active Accounts
*/
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator([
SELECT Id, Name, BillingPostalCode, Is_Potential_Duplicate__c
FROM Account
WHERE IsDeleted = FALSE
AND Is_Potential_Duplicate__c = FALSE
ORDER BY Name ASC
]);
}
/**
* @description Processes each batch by building a duplicate key and checking for matches.
* Uses a composite key of normalized Name + BillingPostalCode.
* @param bc The batch context
* @param scope List of Account records in the current batch
*/
public void execute(Database.BatchableContext bc, List<Account> scope) {
totalScanned += scope.size();
// Build duplicate keys for this batch
Map<String, List<Account>> dupeKeyMap = new Map<String, List<Account>>();
for (Account acct : scope) {
String key = buildDuplicateKey(acct);
if (String.isNotBlank(key)) {
if (!dupeKeyMap.containsKey(key)) {
dupeKeyMap.put(key, new List<Account>());
}
dupeKeyMap.get(key).add(acct);
}
}
// Flag records that share a key with another record
List<Account> toUpdate = new List<Account>();
for (String key : dupeKeyMap.keySet()) {
List<Account> group = dupeKeyMap.get(key);
if (group.size() > 1) {
for (Account acct : group) {
toUpdate.add(new Account(
Id = acct.Id,
Is_Potential_Duplicate__c = true
));
}
}
}
if (!toUpdate.isEmpty()) {
List<Database.SaveResult> results = Database.update(toUpdate, false);
processResults(results);
}
}
/**
* @description Logs a summary of the deduplication batch run
* @param bc The batch context
*/
public void finish(Database.BatchableContext bc) {
String summary = String.format(
'AccountDeduplicationBatch completed. Scanned: {0}, Duplicates flagged: {1}, Errors: {2}',
new List<Object>{ totalScanned, duplicatesFound, totalErrors }
);
System.debug(LoggingLevel.INFO, summary);
if (!errorMessages.isEmpty()) {
System.debug(LoggingLevel.ERROR, 'Error details:\n' + String.join(errorMessages, '\n'));
}
}
// Private Helpers
/**
* @description Builds a normalized composite key for duplicate detection
* @param acct The Account record
* @return Normalized key string, or null if insufficient data
*/
private String buildDuplicateKey(Account acct) {
if (String.isBlank(acct.Name)) {
return null;
}
String normalizedName = acct.Name.trim().toUpperCase().replaceAll('\\s+', ' ');
String postalCode = (acct.BillingPostalCode ?? '').trim().toUpperCase();
return normalizedName + '|' + postalCode;
}
/**
* @description Processes DML results, tracking successes and failures
* @param results List of Database.SaveResult from update operation
*/
private void processResults(List<Database.SaveResult> results) {
for (Database.SaveResult sr : results) {
if (sr.isSuccess()) {
duplicatesFound++;
} else {
totalErrors++;
for (Database.Error err : sr.getErrors()) {
errorMessages.add(
'Record ' + sr.getId() + ': ' +
err.getStatusCode() + ' - ' + err.getMessage()
);
}
}
}
}
// Static Helpers
/**
* @description Convenience method to execute with default batch size
* @return The batch job Id
*/
public static Id run() {
return Database.executeBatch(new AccountDeduplicationBatch(), DEFAULT_BATCH_SIZE);
}
}