initial apex-class commit

This commit is contained in:
Jeff Douglas 2026-02-10 11:57:55 -05:00
parent 573249152f
commit 2adbaed719
15 changed files with 1759 additions and 0 deletions

View File

@ -0,0 +1,253 @@
---
name: apex-class
description: Generate production-ready Apex classes for Salesforce following enterprise best practices. Covers service classes, selectors, domain classes, batch/queueable/schedulable, DTOs, utilities, interfaces, abstract classes, and custom exceptions. Not for triggers or unit tests.
---
# Apex Class
Generate well-structured, production-ready Apex classes for Salesforce development.
## Scope
**Generates:**
- Service classes (business logic layer)
- Selector / query classes (SOQL encapsulation)
- Domain classes (SObject-specific logic)
- Batch Apex classes (`Database.Batchable`)
- Queueable Apex classes (`Queueable`, optionally `Finalizer`)
- Schedulable Apex classes (`Schedulable`)
- DTO / wrapper / request-response classes
- Utility / helper classes
- Custom exception classes
- Interfaces and abstract classes
**Does NOT generate:**
- Triggers (use a trigger framework skill)
- Unit tests (use a test writer skill)
- Aura controllers
- LWC JavaScript controllers
---
## Gathering Requirements
Before generating code, gather these inputs from the user (ask if not provided):
1. **Class type** — Service, Selector, Batch, Queueable, Schedulable, Domain, DTO, Utility, Interface, Abstract, Exception
2. **Class name** — or enough context to derive a meaningful name
3. **SObject(s) involved** — if applicable
4. **Business requirements** — plain-language description of what the class should do
5. **Optional preferences:**
- Sharing model (`with sharing`, `without sharing`, `inherited sharing`)
- Access modifier (`public` or `global`)
- API version (default: `62.0`)
- Whether to include ApexDoc comments (default: yes)
If the user provides a clear, complete request, generate immediately without unnecessary back-and-forth.
---
## Code Standards — ALWAYS Follow These
### Separation of Concerns
- **Service classes** contain business logic. They call Selectors for data and may call Domain classes for SObject-specific behavior.
- **Selector classes** encapsulate all SOQL queries. Services never contain inline SOQL.
- **Domain classes** encapsulate SObject-specific logic (field defaults, validation, transformation).
- Keep classes focused on a single responsibility.
### Bulkification
- All methods must operate on collections (`List`, `Set`, `Map`) by default.
- Never accept a single SObject when a `List<SObject>` is appropriate.
- Provide convenience overloads for single-record callers only when it makes the API cleaner, and have them delegate to the bulk method.
### Governor Limit Safety
- **No SOQL or DML inside loops.** Ever.
- Collect IDs/records first, query/DML once outside the loop.
- Use `Limits` class checks in batch/bulk operations where appropriate.
- Prefer `Database.insert(records, false)` with error handling in batch contexts.
### Sharing Model
- Default to `with sharing` unless the user specifies otherwise.
- If `without sharing` or `inherited sharing` is used, add an ApexDoc `@description` comment explaining WHY.
### Naming Conventions
| Class Type | Pattern | Example |
|-------------|-------------------------------|-------------------------------|
| Service | `{SObject}Service` | `AccountService` |
| Selector | `{SObject}Selector` | `AccountSelector` |
| Domain | `{SObject}Domain` | `OpportunityDomain` |
| Batch | `{Descriptive}Batch` | `AccountDeduplicationBatch` |
| Queueable | `{Descriptive}Queueable` | `ExternalSyncQueueable` |
| Schedulable | `{Descriptive}Schedulable` | `DailyCleanupSchedulable` |
| DTO | `{Descriptive}DTO` | `AccountMergeRequestDTO` |
| Wrapper | `{Descriptive}Wrapper` | `OpportunityLineWrapper` |
| Utility | `{Descriptive}Util` | `StringUtil`, `DateUtil` |
| Interface | `I{Descriptive}` | `INotificationService` |
| Abstract | `Abstract{Descriptive}` | `AbstractIntegrationService` |
| Exception | `{Descriptive}Exception` | `AccountServiceException` |
### ApexDoc Comments
Include ApexDoc on every `public` and `global` method and on the class itself:
```apex
/**
* @description Brief description of what the class does
* @author Generated by Apex Class Writer Skill
*/
```
```apex
/**
* @description Brief description of what the method does
* @param paramName Description of the parameter
* @return Description of the return value
* @example
* List<Account> results = AccountService.deduplicateAccounts(accountIds);
*/
```
### Null Safety
- Use guard clauses at the top of methods for null/empty inputs.
- Use safe navigation (`?.`) where appropriate.
- Return empty collections rather than `null`.
### Constants
- No magic strings or numbers in logic.
- Use `private static final` constants or a dedicated constants class.
- Use `Label.` custom labels for user-facing strings when appropriate.
### Custom Exceptions
- Each service class should define or reference a corresponding custom exception.
- Inner exception classes are preferred for simple cases: `public class AccountServiceException extends Exception {}`
- Include meaningful error messages with context.
### Error Handling
- Catch specific exceptions, not generic `Exception` unless re-throwing.
- Log errors meaningfully (assume a logging utility exists or stub one).
- In batch contexts, use `Database.SaveResult` / `Database.UpsertResult` with partial success.
---
## Output Format
For every class, produce TWO files:
1. **`{ClassName}.cls`** — The Apex class source code
2. **`{ClassName}.cls-meta.xml`** — The metadata file
### Meta XML Template
```xml
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>{API_VERSION}</apiVersion>
<status>Active</status>
</ApexClass>
```
Default `apiVersion` is `62.0` unless the user specifies otherwise.
---
## Class TypeSpecific Instructions
### Service Classes
- Read and follow: `templates/service.cls`
- Stateless — no instance variables holding mutable state
- All public methods should be `static` unless there's a compelling reason for instance methods
- Delegate queries to a Selector class
- Wrap business logic errors in a custom exception
### Selector Classes
- Read and follow: `templates/selector.cls`
- One Selector per SObject (or per logical query domain)
- Return `List<SObject>` or `Map<Id, SObject>`
- Accept filter criteria as method parameters, not hardcoded
- Include a private method that returns the base field list to keep DRY
### Domain Classes
- Read and follow: `templates/domain.cls`
- Encapsulate field-level defaults, derivations, and validations
- Operate on `List<SObject>` — designed to be called from triggers or services
- No SOQL or DML — only in-memory SObject manipulation
### Batch Classes
- Read and follow: `templates/batch.cls`
- Implement `Database.Batchable<SObject>` and optionally `Database.Stateful`
- Use `Database.QueryLocator` in `start()` for large datasets
- Handle partial failures in `execute()` using `Database.SaveResult`
- Implement meaningful `finish()` — at minimum, log completion
### Queueable Classes
- Read and follow: `templates/queueable.cls`
- Implement `Queueable` and optionally `Database.AllowsCallouts`
- Accept data through the constructor — queueables are stateful
- For chaining, include guard logic to prevent infinite chains
- Optionally implement `Finalizer` for error recovery
### Schedulable Classes
- Read and follow: `templates/schedulable.cls`
- Implement `Schedulable`
- Keep `execute()` lightweight — delegate to a Batch or Queueable
- Include a static method that returns a CRON expression for convenience
- Document the expected schedule in ApexDoc
### DTO / Wrapper Classes
- Read and follow: `templates/dto.cls`
- Use `public` properties — no getters/setters unless validation is needed
- Include a no-arg constructor and optionally a parameterized constructor
- Implement `Comparable` if sorting is needed
- Keep them serialization-friendly (no transient state unless intentional)
### Utility Classes
- Read and follow: `templates/utility.cls`
- All methods `public static`
- Class should be `public with sharing` with a `private` constructor to prevent instantiation
- Group related utilities (e.g., `StringUtil`, `DateUtil`, `CollectionUtil`)
- Every method must be side-effect-free (no DML, no SOQL)
### Interfaces
- Read and follow: `templates/interface.cls`
- Define the contract clearly with ApexDoc on every method signature
- Use meaningful names that describe the capability: `INotificationService`, `IRetryable`
### Abstract Classes
- Read and follow: `templates/abstract.cls`
- Provide default implementations for common behavior
- Mark extension points as `protected virtual` or `protected abstract`
- Include a concrete example in the ApexDoc showing how to extend
### Custom Exceptions
- Read and follow: `templates/exception.cls`
- Extend `Exception`
- Keep them simple — Apex exceptions don't support custom constructors well
- Name them descriptively: `AccountServiceException`, `IntegrationTimeoutException`
---
## Generation Workflow
1. Determine the class type from the user's request
2. Read the corresponding template from `templates/`
3. Read relevant examples from `examples/` if the class type has one
4. Apply the user's requirements to the template pattern
5. Generate the `.cls` file with full ApexDoc
6. Generate the `.cls-meta.xml` file
7. Present both files to the user
8. Include a brief note on design decisions if any non-obvious choices were made
---
## Anti-Patterns to Avoid
- ❌ SOQL or DML inside loops
- ❌ Hardcoded IDs or record type names (use `Schema.SObjectType` or Custom Metadata)
- ❌ God classes that mix query + logic + DML
- ❌ `public` fields on service classes
- ❌ Returning `null` from methods that should return collections
- ❌ Generic `catch (Exception e)` without re-throwing or meaningful handling
- ❌ Business logic in Batch `start()` methods
- ❌ Tight coupling between classes — use interfaces for extensibility
- ❌ Magic strings or numbers
- ❌ Methods longer than ~40 lines — break them into private helpers

View File

@ -0,0 +1,148 @@
/**
* @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);
}
}

View File

@ -0,0 +1,193 @@
/**
* @description Selector class for Account queries.
* Encapsulates all SOQL for Account records.
* All methods return bulkified results (Lists or Maps).
* @author Generated by Apex Class Writer Skill
*/
public with sharing class AccountSelector {
// ─── Field Lists ─────────────────────────────────────────────────────
/**
* @description Returns the default set of fields to query for Account.
* Centralizes field references to keep queries DRY.
* @return Comma-separated field list as a String
*/
private static String getDefaultFields() {
return String.join(
new List<String>{
'Id',
'Name',
'AccountNumber',
'Type',
'Industry',
'AnnualRevenue',
'NumberOfEmployees',
'Phone',
'Website',
'OwnerId',
'CreatedDate',
'LastModifiedDate'
},
', '
);
}
/**
* @description Returns fields needed for billing/territory operations
* @return Comma-separated field list as a String
*/
private static String getBillingFields() {
return String.join(
new List<String>{
'Id',
'Name',
'BillingStreet',
'BillingCity',
'BillingState',
'BillingPostalCode',
'BillingCountry',
'Territory__c'
},
', '
);
}
// ─── Query Methods ───────────────────────────────────────────────────
/**
* @description Selects Account records by their Ids
* @param recordIds Set of Account Ids to query
* @return List of Account records matching the provided Ids
* @example
* Set<Id> ids = new Set<Id>{ '001xx000003DGbY' };
* List<Account> results = AccountSelector.selectByIds(ids);
*/
public static List<Account> selectByIds(Set<Id> recordIds) {
if (recordIds == null || recordIds.isEmpty()) {
return new List<Account>();
}
return Database.query(
'SELECT ' + getDefaultFields() +
' FROM Account' +
' WHERE Id IN :recordIds'
);
}
/**
* @description Selects Account records as a Map keyed by Id
* @param recordIds Set of Account Ids to query
* @return Map of Id to Account
*/
public static Map<Id, Account> selectMapByIds(Set<Id> recordIds) {
return new Map<Id, Account>(selectByIds(recordIds));
}
/**
* @description Selects Accounts with billing address fields for territory assignment
* @param recordIds Set of Account Ids to query
* @return List of Account records with billing address fields populated
*/
public static List<Account> selectWithBillingAddress(Set<Id> recordIds) {
if (recordIds == null || recordIds.isEmpty()) {
return new List<Account>();
}
return Database.query(
'SELECT ' + getBillingFields() +
' FROM Account' +
' WHERE Id IN :recordIds'
);
}
/**
* @description Selects Accounts by Account Type
* @param accountTypes Set of Account Type values to filter by
* @return List of matching Account records
* @example
* List<Account> prospects = AccountSelector.selectByType(
* new Set<String>{ 'Prospect', 'Customer - Direct' }
* );
*/
public static List<Account> selectByType(Set<String> accountTypes) {
if (accountTypes == null || accountTypes.isEmpty()) {
return new List<Account>();
}
return Database.query(
'SELECT ' + getDefaultFields() +
' FROM Account' +
' WHERE Type IN :accountTypes' +
' ORDER BY Name ASC'
);
}
/**
* @description Selects Accounts by Industry with a minimum annual revenue
* @param industries Set of Industry values to filter by
* @param minRevenue Minimum AnnualRevenue threshold
* @return List of matching Account records ordered by revenue descending
* @example
* List<Account> techAccounts = AccountSelector.selectByIndustryAndRevenue(
* new Set<String>{ 'Technology' },
* 1000000
* );
*/
public static List<Account> selectByIndustryAndRevenue(Set<String> industries, Decimal minRevenue) {
if (industries == null || industries.isEmpty()) {
return new List<Account>();
}
Decimal revenueThreshold = minRevenue ?? 0;
return Database.query(
'SELECT ' + getDefaultFields() +
' FROM Account' +
' WHERE Industry IN :industries' +
' AND AnnualRevenue >= :revenueThreshold' +
' ORDER BY AnnualRevenue DESC'
);
}
/**
* @description Selects Accounts with their related Contacts (subquery)
* @param recordIds Set of Account Ids to query
* @return List of Account records with nested Contacts
*/
public static List<Account> selectWithContacts(Set<Id> recordIds) {
if (recordIds == null || recordIds.isEmpty()) {
return new List<Account>();
}
return [
SELECT Id, Name, Type, Industry,
(SELECT Id, FirstName, LastName, Email, Title
FROM Contacts
ORDER BY LastName ASC)
FROM Account
WHERE Id IN :recordIds
];
}
// ─── Aggregate Queries ───────────────────────────────────────────────
/**
* @description Returns a count of Accounts grouped by Industry
* @return List of AggregateResult with Industry and record count
* @example
* List<AggregateResult> results = AccountSelector.countByIndustry();
* for (AggregateResult ar : results) {
* System.debug(ar.get('Industry') + ': ' + ar.get('cnt'));
* }
*/
public static List<AggregateResult> countByIndustry() {
return [
SELECT Industry, COUNT(Id) cnt
FROM Account
WHERE Industry != NULL
GROUP BY Industry
ORDER BY COUNT(Id) DESC
];
}
}

View File

@ -0,0 +1,201 @@
/**
* @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 {}
}

View File

@ -0,0 +1,128 @@
/**
* @description Abstract base class for {describe the family of classes this serves}.
* Provides common behavior and defines extension points for subclasses.
* Subclasses must implement the abstract methods to provide specific behavior.
* @author Generated by Apex Class Writer Skill
*
* @example
* // Extending this abstract class:
* public class SalesforceIntegrationService extends {ClassName} {
* protected override String getEndpoint() {
* return 'callout:Salesforce_API/services/data/v62.0';
* }
*
* protected override Map<String, String> getHeaders() {
* return new Map<String, String>{
* 'Content-Type' => 'application/json'
* };
* }
* }
*/
public abstract with sharing class {ClassName} {
// ─── Constants ───────────────────────────────────────────────────────
private static final Integer DEFAULT_TIMEOUT_MS = 30000;
// ─── Protected State ─────────────────────────────────────────────────
protected Integer timeoutMs;
// ─── Constructor ─────────────────────────────────────────────────────
/**
* @description Initializes the base class with default configuration
*/
protected {ClassName}() {
this.timeoutMs = DEFAULT_TIMEOUT_MS;
}
// ─── Abstract Methods (must be implemented by subclasses) ────────────
/**
* @description Returns the endpoint URL for this integration.
* Subclasses must provide their specific endpoint.
* @return The endpoint URL as a String
*/
protected abstract String getEndpoint();
/**
* @description Returns the HTTP headers for this integration.
* Subclasses define their own required headers.
* @return Map of header name to header value
*/
protected abstract Map<String, String> getHeaders();
// ─── Virtual Methods (can be overridden by subclasses) ───────────────
/**
* @description Hook called before the main operation executes.
* Override to add pre-processing logic.
* Default implementation does nothing.
* @param context Map of contextual data
*/
protected virtual void beforeExecute(Map<String, Object> context) {
// Default: no-op — override in subclass if needed
}
/**
* @description Hook called after the main operation completes.
* Override to add post-processing logic.
* Default implementation does nothing.
* @param context Map of contextual data
* @param result The result from the operation
*/
protected virtual void afterExecute(Map<String, Object> context, Object result) {
// Default: no-op — override in subclass if needed
}
// ─── Template Method (common workflow) ───────────────────────────────
/**
* @description Executes the operation using the template method pattern.
* Calls beforeExecute doExecute afterExecute in sequence.
* @param context Map of data needed for the operation
* @return The result of the operation
*/
public Object execute(Map<String, Object> context) {
beforeExecute(context);
Object result;
try {
result = doExecute(context);
} catch (Exception e) {
handleError(e);
throw e;
}
afterExecute(context, result);
return result;
}
// ─── Protected Helpers ───────────────────────────────────────────────
/**
* @description Core execution logic override this for the main operation.
* Default implementation throws subclass must provide implementation.
* @param context Map of data needed for the operation
* @return The result of the operation
*/
protected virtual Object doExecute(Map<String, Object> context) {
throw new UnsupportedOperationException(
'Subclass must override doExecute()'
);
}
/**
* @description Error handler called when doExecute throws.
* Override to customize error handling (e.g., logging, retry).
* @param e The exception that was thrown
*/
protected virtual void handleError(Exception e) {
System.debug(LoggingLevel.ERROR,
this.toString() + ' error: ' + e.getMessage() + '\n' + e.getStackTraceString()
);
}
// ─── Exception ───────────────────────────────────────────────────────
public class UnsupportedOperationException extends Exception {}
}

View File

@ -0,0 +1,125 @@
/**
* @description Batch Apex class for {describe the batch operation}.
* Processes {SObject} records in configurable batch sizes.
* Implements Database.Stateful to track cumulative results across chunks.
* @author Generated by Apex Class Writer Skill
*
* @example
* // Execute with default batch size
* Database.executeBatch(new {ClassName}());
*
* // Execute with custom batch size
* Database.executeBatch(new {ClassName}(), 100);
*/
public with sharing class {ClassName} implements Database.Batchable<SObject>, Database.Stateful {
// ─── Constants ───────────────────────────────────────────────────────
private static final Integer DEFAULT_BATCH_SIZE = 200;
// ─── Stateful Tracking ───────────────────────────────────────────────
private Integer totalProcessed = 0;
private Integer totalErrors = 0;
private List<String> errorMessages = new List<String>();
// ─── Constructor ─────────────────────────────────────────────────────
/**
* @description Default constructor
*/
public {ClassName}() {
// Default configuration
}
// ─── Batchable Interface ─────────────────────────────────────────────
/**
* @description Defines the scope of records to process.
* Uses Database.QueryLocator for efficient large-dataset processing.
* @param bc The batch context
* @return QueryLocator for the records to process
*/
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator([
SELECT Id, Name
// TODO: Add fields needed for processing
FROM {SObject}
// TODO: Add WHERE clause to scope the records
]);
}
/**
* @description Processes each batch of records.
* Uses Database.update with allOrNone=false for partial success handling.
* @param bc The batch context
* @param scope List of {SObject} records in the current batch
*/
public void execute(Database.BatchableContext bc, List<{SObject}> scope) {
List<{SObject}> recordsToUpdate = new List<{SObject}>();
for ({SObject} record : scope) {
// TODO: Apply business logic to each record
recordsToUpdate.add(record);
}
if (!recordsToUpdate.isEmpty()) {
List<Database.SaveResult> results = Database.update(recordsToUpdate, false);
processResults(results);
}
}
/**
* @description Performs post-processing after all batches complete.
* Logs a summary of the batch execution.
* @param bc The batch context
*/
public void finish(Database.BatchableContext bc) {
String summary = String.format(
'{0} completed. Processed: {1}, Errors: {2}',
new List<Object>{
{ClassName}.class.getName(),
totalProcessed,
totalErrors
}
);
System.debug(LoggingLevel.INFO, summary);
if (!errorMessages.isEmpty()) {
System.debug(LoggingLevel.ERROR, 'Error details: ' + String.join(errorMessages, '\n'));
}
// TODO: Send completion notification email or post to chatter if needed
}
// ─── Private Helpers ─────────────────────────────────────────────────
/**
* @description Processes Database.SaveResult list, tracking successes and failures
* @param results List of SaveResult from a DML operation
*/
private void processResults(List<Database.SaveResult> results) {
for (Database.SaveResult result : results) {
if (result.isSuccess()) {
totalProcessed++;
} else {
totalErrors++;
for (Database.Error err : result.getErrors()) {
errorMessages.add(
'Record ' + result.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 {ClassName}(), DEFAULT_BATCH_SIZE);
}
}

View File

@ -0,0 +1,102 @@
/**
* @description Domain class for {SObject}.
* Encapsulates field-level defaults, derivations, and validations.
* Operates only on in-memory SObject data no SOQL or DML.
* @author Generated by Apex Class Writer Skill
*/
public with sharing class {SObject}Domain {
// ─── Constants ───────────────────────────────────────────────────────
// TODO: Add constants for default values, statuses, etc.
// ─── Field Defaults ──────────────────────────────────────────────────
/**
* @description Applies default field values to new {SObject} records.
* Call this before insert to ensure consistent defaults.
* @param records List of {SObject} records to apply defaults to
*/
public static void applyDefaults(List<{SObject}> records) {
if (records == null || records.isEmpty()) {
return;
}
for ({SObject} record : records) {
// TODO: Set default field values
// Example:
// if (record.Status__c == null) {
// record.Status__c = DEFAULT_STATUS;
// }
}
}
// ─── Derivations ────────────────────────────────────────────────────
/**
* @description Derives calculated field values based on other fields.
* Call this before insert and before update.
* @param records List of {SObject} records to derive values for
*/
public static void deriveFields(List<{SObject}> records) {
if (records == null || records.isEmpty()) {
return;
}
for ({SObject} record : records) {
// TODO: Derive calculated field values
// Example:
// record.FullAddress__c = buildFullAddress(record);
}
}
// ─── Validations ────────────────────────────────────────────────────
/**
* @description Validates {SObject} records and adds errors for any violations.
* Call this before insert and before update.
* @param records List of {SObject} records to validate
*/
public static void validate(List<{SObject}> records) {
if (records == null || records.isEmpty()) {
return;
}
for ({SObject} record : records) {
// TODO: Add validation rules
// Example:
// if (String.isBlank(record.Name)) {
// record.addError('Name is required.');
// }
}
}
// ─── Comparisons ────────────────────────────────────────────────────
/**
* @description Determines which fields have changed between old and new record versions.
* Useful in before update context.
* @param oldRecord The previous version of the record
* @param newRecord The current version of the record
* @return Set of field API names that have changed
*/
public static Set<String> getChangedFields({SObject} oldRecord, {SObject} newRecord) {
Set<String> changedFields = new Set<String>();
if (oldRecord == null || newRecord == null) {
return changedFields;
}
Map<String, Schema.SObjectField> fieldMap = Schema.SObjectType.{SObject}.fields.getMap();
for (String fieldName : fieldMap.keySet()) {
if (oldRecord.get(fieldName) != newRecord.get(fieldName)) {
changedFields.add(fieldName);
}
}
return changedFields;
}
// ─── Private Helpers ─────────────────────────────────────────────────
// TODO: Add private helper methods as needed
}

View File

@ -0,0 +1,108 @@
/**
* @description Data Transfer Object for {describe the data this DTO represents}.
* Used to pass structured data between layers without exposing SObjects.
* Serialization-friendly for use with JSON.serialize/deserialize and API responses.
* @author Generated by Apex Class Writer Skill
*
* @example
* // Create from constructor
* {ClassName} dto = new {ClassName}('value1', 42);
*
* // Deserialize from JSON
* {ClassName} dto = ({ClassName}) JSON.deserialize(jsonString, {ClassName}.class);
*/
public with sharing class {ClassName} {
// ─── Properties ──────────────────────────────────────────────────────
/** @description {Describe this property} */
public String name { get; set; }
/** @description {Describe this property} */
public Id recordId { get; set; }
/** @description {Describe this property} */
public Boolean isActive { get; set; }
/** @description {Describe this property} */
public List<String> tags { get; set; }
// TODO: Add additional properties as needed
// ─── Constructors ────────────────────────────────────────────────────
/**
* @description No-arg constructor for deserialization compatibility
*/
public {ClassName}() {
this.tags = new List<String>();
this.isActive = false;
}
/**
* @description Parameterized constructor for convenience
* @param name The name value
* @param recordId The associated record Id
*/
public {ClassName}(String name, Id recordId) {
this();
this.name = name;
this.recordId = recordId;
}
// ─── Factory Methods ─────────────────────────────────────────────────
/**
* @description Creates a DTO instance from an SObject record
* @param record The source {SObject} record
* @return A populated {ClassName} instance
*/
public static {ClassName} fromSObject(SObject record) {
if (record == null) {
return new {ClassName}();
}
{ClassName} dto = new {ClassName}();
dto.recordId = record.Id;
dto.name = (String) record.get('Name');
// TODO: Map additional fields
return dto;
}
/**
* @description Creates a list of DTOs from a list of SObject records
* @param records The source records
* @return List of populated {ClassName} instances
*/
public static List<{ClassName}> fromSObjects(List<SObject> records) {
List<{ClassName}> dtos = new List<{ClassName}>();
if (records == null) {
return dtos;
}
for (SObject record : records) {
dtos.add(fromSObject(record));
}
return dtos;
}
// ─── Utility Methods ─────────────────────────────────────────────────
/**
* @description Serializes this DTO to a JSON string
* @return JSON representation of this DTO
*/
public String toJson() {
return JSON.serialize(this);
}
/**
* @description Deserializes a JSON string into a {ClassName} instance
* @param jsonString The JSON string to deserialize
* @return A {ClassName} instance
*/
public static {ClassName} fromJson(String jsonString) {
return ({ClassName}) JSON.deserialize(jsonString, {ClassName}.class);
}
}

View File

@ -0,0 +1,51 @@
/**
* @description Custom exception for {describe when this exception is thrown}.
* Use this exception to signal domain-specific errors that callers
* can catch and handle distinctly from system exceptions.
* @author Generated by Apex Class Writer Skill
*
* @example
* throw new {ClassName}('Account merge failed: duplicate detected.');
*
* // Wrap a caught exception
* try {
* // ... risky operation
* } catch (DmlException e) {
* throw new {ClassName}('DML failed during account merge: ' + e.getMessage());
* }
*/
public with sharing class {ClassName} extends Exception {
// Apex custom exceptions automatically inherit:
// - getMessage()
// - getCause()
// - getStackTraceString()
// - setMessage(String)
// - initCause(Exception)
//
// And support these constructor patterns:
// - new {ClassName}()
// - new {ClassName}('message')
// - new {ClassName}(causeException)
// - new {ClassName}('message', causeException)
//
// Note: Apex does NOT support custom constructors on Exception subclasses.
// To add context, use the message string or create a wrapper pattern:
//
// Example wrapper pattern (if you need structured error data):
//
// public class {ClassName}Detail {
// public String errorCode;
// public List<Id> failedRecordIds;
// public String detail;
//
// public {ClassName}Detail(String errorCode, List<Id> failedRecordIds, String detail) {
// this.errorCode = errorCode;
// this.failedRecordIds = failedRecordIds;
// this.detail = detail;
// }
//
// public override String toString() {
// return '[' + errorCode + '] ' + detail + ' (Records: ' + failedRecordIds + ')';
// }
// }
}

View File

@ -0,0 +1,25 @@
/**
* @description Interface for {describe the capability or contract this interface defines}.
* Implement this interface to provide {describe what implementations do}.
* @author Generated by Apex Class Writer Skill
*
* @example
* public class EmailNotificationService implements {InterfaceName} {
* public void execute(Map<String, Object> params) {
* // Implementation
* }
* }
*/
public interface {InterfaceName} {
/**
* @description {Describe what this method should do}
* @param params {Describe the parameter}
* @return {Describe the return value}
*/
// TODO: Define interface methods
// Example:
// void execute(Map<String, Object> params);
// Boolean isEligible(SObject record);
// List<SObject> process(List<SObject> records);
}

View File

@ -0,0 +1,92 @@
/**
* @description Queueable Apex class for {describe the async operation}.
* Accepts data through the constructor for stateful processing.
* Optionally implements Database.AllowsCallouts for external integrations.
* @author Generated by Apex Class Writer Skill
*
* @example
* // Enqueue the job
* Id jobId = System.enqueueJob(new {ClassName}(recordIds));
*/
public with sharing class {ClassName} implements Queueable /*, Database.AllowsCallouts */ {
// ─── Constants ───────────────────────────────────────────────────────
private static final Integer MAX_CHAIN_DEPTH = 5;
// ─── Instance Variables (Stateful) ───────────────────────────────────
private Set<Id> recordIds;
private Integer chainDepth;
// ─── Constructors ────────────────────────────────────────────────────
/**
* @description Creates a new queueable job to process the specified records
* @param recordIds Set of record Ids to process
*/
public {ClassName}(Set<Id> recordIds) {
this(recordIds, 0);
}
/**
* @description Creates a new queueable job with chain depth tracking
* @param recordIds Set of record Ids to process
* @param chainDepth Current depth in the queueable chain
*/
public {ClassName}(Set<Id> recordIds, Integer chainDepth) {
this.recordIds = recordIds ?? new Set<Id>();
this.chainDepth = chainDepth;
}
// ─── Queueable Interface ─────────────────────────────────────────────
/**
* @description Executes the asynchronous work
* @param context The queueable context
*/
public void execute(QueueableContext context) {
if (this.recordIds.isEmpty()) {
return;
}
try {
// TODO: Implement the async processing logic
// List<{SObject}> records = {SObject}Selector.selectByIds(this.recordIds);
// ... process records ...
// Chain to next job if there's more work and we haven't hit the depth limit
chainIfNeeded();
} catch (Exception e) {
handleError(context.getJobId(), e);
}
}
// ─── Private Helpers ─────────────────────────────────────────────────
/**
* @description Chains to the next queueable job if needed, with depth guard
*/
private void chainIfNeeded() {
// TODO: Determine if chaining is needed (e.g., remaining records to process)
Set<Id> remainingIds = new Set<Id>();
if (!remainingIds.isEmpty() && this.chainDepth < MAX_CHAIN_DEPTH) {
if (!Test.isRunningTest()) {
System.enqueueJob(new {ClassName}(remainingIds, this.chainDepth + 1));
}
}
}
/**
* @description Handles errors during execution
* @param jobId The async job Id
* @param e The exception that occurred
*/
private void handleError(Id jobId, Exception e) {
System.debug(LoggingLevel.ERROR,
'{ClassName} failed (Job: ' + jobId + '): ' +
e.getMessage() + '\n' + e.getStackTraceString()
);
// TODO: Persist error to a log object or send notification
}
}

View File

@ -0,0 +1,75 @@
/**
* @description Schedulable Apex class for {describe the scheduled operation}.
* Delegates heavy processing to a Batch or Queueable job.
* Keep execute() lightweight it should only launch other jobs.
* @author Generated by Apex Class Writer Skill
*
* @example
* // Schedule to run daily at 2 AM
* String jobId = System.schedule(
* '{ClassName} - Daily',
* {ClassName}.CRON_DAILY_2AM,
* new {ClassName}()
* );
*
* // Or use the convenience method
* String jobId = {ClassName}.scheduleDaily();
*/
public with sharing class {ClassName} implements Schedulable {
// ─── CRON Expressions ────────────────────────────────────────────────
// Seconds Minutes Hours Day_of_month Month Day_of_week Optional_year
/** @description Runs daily at 2:00 AM */
public static final String CRON_DAILY_2AM = '0 0 2 * * ?';
/** @description Runs every weekday at 6:00 AM */
public static final String CRON_WEEKDAYS_6AM = '0 0 6 ? * MON-FRI';
/** @description Runs hourly at the top of the hour */
public static final String CRON_HOURLY = '0 0 * * * ?';
// ─── Schedulable Interface ───────────────────────────────────────────
/**
* @description Entry point for the scheduled execution.
* Delegates to a Batch or Queueable for the actual work.
* @param sc The schedulable context
*/
public void execute(SchedulableContext sc) {
// Option A: Launch a Batch job
// Database.executeBatch(new {BatchClassName}(), 200);
// Option B: Launch a Queueable job
// System.enqueueJob(new {QueueableClassName}(params));
// TODO: Implement delegation to appropriate async job
}
// ─── Convenience Scheduling Methods ──────────────────────────────────
/**
* @description Schedules this job to run daily at 2 AM
* @return The scheduled job Id
*/
public static String scheduleDaily() {
return System.schedule(
'{ClassName} - Daily 2AM',
CRON_DAILY_2AM,
new {ClassName}()
);
}
/**
* @description Aborts this scheduled job by name
* @param jobName The name used when scheduling
*/
public static void abort(String jobName) {
for (CronTrigger ct : [
SELECT Id FROM CronTrigger
WHERE CronJobDetail.Name = :jobName
]) {
System.abortJob(ct.Id);
}
}
}

View File

@ -0,0 +1,92 @@
/**
* @description Selector class for {SObject} queries.
* Encapsulates all SOQL for {SObject} records.
* All methods return bulkified results (Lists or Maps).
* @author Generated by Apex Class Writer Skill
*/
public with sharing class {SObject}Selector {
// ─── Field Lists ─────────────────────────────────────────────────────
/**
* @description Returns the default set of fields to query for {SObject}.
* Centralizes field references to keep queries DRY.
* @return Comma-separated field list as a String
*/
private static String getDefaultFields() {
return String.join(
new List<String>{
'Id',
'Name',
'CreatedDate',
'LastModifiedDate'
// TODO: Add additional fields here
},
', '
);
}
// ─── Query Methods ───────────────────────────────────────────────────
/**
* @description Selects {SObject} records by their Ids
* @param recordIds Set of {SObject} Ids to query
* @return List of {SObject} records matching the provided Ids
* @example
* Set<Id> ids = new Set<Id>{ '001xx000003DGbY' };
* List<{SObject}> results = {SObject}Selector.selectByIds(ids);
*/
public static List<{SObject}> selectByIds(Set<Id> recordIds) {
if (recordIds == null || recordIds.isEmpty()) {
return new List<{SObject}>();
}
return Database.query(
'SELECT ' + getDefaultFields() +
' FROM {SObject}' +
' WHERE Id IN :recordIds'
);
}
/**
* @description Selects {SObject} records as a Map keyed by Id
* @param recordIds Set of {SObject} Ids to query
* @return Map of Id to {SObject}
*/
public static Map<Id, {SObject}> selectMapByIds(Set<Id> recordIds) {
return new Map<Id, {SObject}>(selectByIds(recordIds));
}
/**
* @description Selects {SObject} records by a specific field value
* @param fieldName API name of the field to filter on
* @param values Set of values to match
* @return List of matching {SObject} records
* @example
* List<{SObject}> results = {SObject}Selector.selectByField('Status__c', new Set<String>{ 'Active' });
*/
public static List<{SObject}> selectByField(String fieldName, Set<String> values) {
if (String.isBlank(fieldName) || values == null || values.isEmpty()) {
return new List<{SObject}>();
}
// Validate field name to prevent SOQL injection
Schema.SObjectField field = Schema.SObjectType.{SObject}.fields.getMap().get(fieldName);
if (field == null) {
throw new QueryException('Invalid field name: ' + fieldName);
}
return Database.query(
'SELECT ' + getDefaultFields() +
' FROM {SObject}' +
' WHERE ' + String.escapeSingleQuotes(fieldName) + ' IN :values'
);
}
// ─── Exception ───────────────────────────────────────────────────────
/**
* @description Custom exception for query errors
*/
public class QueryException extends Exception {}
}

View File

@ -0,0 +1,69 @@
/**
* @description Service class for {SObject} business logic.
* Follows separation of concerns: delegates queries to {SObject}Selector
* and SObject manipulation to {SObject}Domain where applicable.
* @author Generated by Apex Class Writer Skill
*/
public with sharing class {SObject}Service {
// ─── Constants ───────────────────────────────────────────────────────
private static final String ERROR_NULL_INPUT = 'Input cannot be null or empty.';
// ─── Public API ──────────────────────────────────────────────────────
/**
* @description {Describe the primary operation}
* @param recordIds Set of {SObject} Ids to process
* @return List of processed {SObject} records
* @throws {SObject}ServiceException if processing fails
* @example
* Set<Id> ids = new Set<Id>{ '001xx000003DGbY' };
* List<{SObject}> results = {SObject}Service.process{SObject}s(ids);
*/
public static List<{SObject}> process{SObject}s(Set<Id> recordIds) {
// Guard clause
if (recordIds == null || recordIds.isEmpty()) {
throw new {SObject}ServiceException(ERROR_NULL_INPUT);
}
// Query via Selector
List<{SObject}> records = {SObject}Selector.selectByIds(recordIds);
// Apply business logic
// TODO: Implement business logic here
// DML
try {
update records;
} catch (DmlException e) {
throw new {SObject}ServiceException(
'Failed to update {SObject} records: ' + e.getMessage()
);
}
return records;
}
// ─── Convenience Overloads ───────────────────────────────────────────
/**
* @description Single-record convenience overload
* @param recordId The {SObject} Id to process
* @return The processed {SObject} record
*/
public static {SObject} process{SObject}(Id recordId) {
List<{SObject}> results = process{SObject}s(new Set<Id>{ recordId });
return results.isEmpty() ? null : results[0];
}
// ─── Private Helpers ─────────────────────────────────────────────────
// TODO: Add private helper methods as needed
// ─── Exception ───────────────────────────────────────────────────────
/**
* @description Custom exception for {SObject}Service errors
*/
public class {SObject}ServiceException extends Exception {}
}

View File

@ -0,0 +1,97 @@
/**
* @description Utility class for {describe the category of utilities: String, Date, Collection, etc.}.
* All methods are static and side-effect-free (no SOQL, no DML).
* Private constructor prevents instantiation.
* @author Generated by Apex Class Writer Skill
*/
public with sharing class {ClassName} {
// ─── Private Constructor ─────────────────────────────────────────────
/**
* @description Prevents instantiation use static methods only
*/
@TestVisible
private {ClassName}() {
// Utility class — do not instantiate
}
// ─── Public Methods ──────────────────────────────────────────────────
// TODO: Add utility methods below. Examples:
/**
* @description Safely converts a String to an Integer, returning a default if parsing fails
* @param value The String to parse
* @param defaultValue The fallback value if parsing fails
* @return The parsed Integer or the default value
* @example
* Integer result = {ClassName}.safeParseInteger('42', 0); // returns 42
* Integer result = {ClassName}.safeParseInteger('abc', 0); // returns 0
*/
public static Integer safeParseInteger(String value, Integer defaultValue) {
if (String.isBlank(value)) {
return defaultValue;
}
try {
return Integer.valueOf(value.trim());
} catch (TypeException e) {
return defaultValue;
}
}
/**
* @description Chunks a list into smaller sublists of the specified size.
* Useful for processing records in governor-limit-safe batches.
* @param items The list to chunk
* @param chunkSize The maximum size of each chunk
* @return A list of sublists, each containing up to chunkSize elements
* @example
* List<List<String>> chunks = {ClassName}.chunkList(myList, 200);
*/
public static List<List<Object>> chunkList(List<Object> items, Integer chunkSize) {
List<List<Object>> chunks = new List<List<Object>>();
if (items == null || items.isEmpty() || chunkSize <= 0) {
return chunks;
}
List<Object> currentChunk = new List<Object>();
for (Object item : items) {
currentChunk.add(item);
if (currentChunk.size() == chunkSize) {
chunks.add(currentChunk);
currentChunk = new List<Object>();
}
}
if (!currentChunk.isEmpty()) {
chunks.add(currentChunk);
}
return chunks;
}
/**
* @description Extracts a Set of non-null field values from a list of SObjects
* @param records The SObject records to extract from
* @param fieldName The API name of the field to extract
* @return A Set of non-null String values
* @example
* Set<String> emails = {ClassName}.pluckStrings(contacts, 'Email');
*/
public static Set<String> pluckStrings(List<SObject> records, String fieldName) {
Set<String> values = new Set<String>();
if (records == null || String.isBlank(fieldName)) {
return values;
}
for (SObject record : records) {
Object val = record.get(fieldName);
if (val != null) {
values.add(String.valueOf(val));
}
}
return values;
}
}