@salesforce/afv-skills 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/skills/creating-webapp/SKILL.md +0 -2
- package/skills/generating-apex/SKILL.md +253 -0
- package/skills/generating-apex/assets/abstract.cls +128 -0
- package/skills/generating-apex/assets/batch.cls +125 -0
- package/skills/generating-apex/assets/domain.cls +102 -0
- package/skills/generating-apex/assets/dto.cls +108 -0
- package/skills/generating-apex/assets/exception.cls +51 -0
- package/skills/generating-apex/assets/interface.cls +25 -0
- package/skills/generating-apex/assets/queueable.cls +92 -0
- package/skills/generating-apex/assets/schedulable.cls +75 -0
- package/skills/generating-apex/assets/selector.cls +92 -0
- package/skills/generating-apex/assets/service.cls +69 -0
- package/skills/generating-apex/assets/utility.cls +97 -0
- package/skills/generating-apex/references/AccountDeduplicationBatch.cls +148 -0
- package/skills/generating-apex/references/AccountSelector.cls +193 -0
- package/skills/generating-apex/references/AccountService.cls +201 -0
- package/skills/generating-apex-test/SKILL.md +108 -0
- package/skills/generating-apex-test/assets/test-class-template.cls +124 -0
- package/skills/generating-apex-test/assets/test-data-factory-template.cls +112 -0
- package/skills/generating-apex-test/references/assertion-patterns.md +165 -0
- package/skills/generating-apex-test/references/async-testing.md +276 -0
- package/skills/generating-apex-test/references/mocking-patterns.md +219 -0
- package/skills/generating-apex-test/references/test-data-factory.md +176 -0
- package/skills/generating-experience-react-site/SKILL.md +11 -0
- package/skills/generating-flexipage/SKILL.md +39 -57
- package/skills/searching-media/SKILL.md +342 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Data Transfer Object for {describe the data this DTO represents}.
|
|
3
|
+
* Used to pass structured data between layers without exposing SObjects.
|
|
4
|
+
* Serialization-friendly for use with JSON.serialize/deserialize and API responses.
|
|
5
|
+
* @author Generated by Apex Class Writer Skill
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Create from constructor
|
|
9
|
+
* {ClassName} dto = new {ClassName}('value1', 42);
|
|
10
|
+
*
|
|
11
|
+
* // Deserialize from JSON
|
|
12
|
+
* {ClassName} dto = ({ClassName}) JSON.deserialize(jsonString, {ClassName}.class);
|
|
13
|
+
*/
|
|
14
|
+
public with sharing class {ClassName} {
|
|
15
|
+
|
|
16
|
+
// ─── Properties ──────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
/** @description {Describe this property} */
|
|
19
|
+
public String name { get; set; }
|
|
20
|
+
|
|
21
|
+
/** @description {Describe this property} */
|
|
22
|
+
public Id recordId { get; set; }
|
|
23
|
+
|
|
24
|
+
/** @description {Describe this property} */
|
|
25
|
+
public Boolean isActive { get; set; }
|
|
26
|
+
|
|
27
|
+
/** @description {Describe this property} */
|
|
28
|
+
public List<String> tags { get; set; }
|
|
29
|
+
|
|
30
|
+
// TODO: Add additional properties as needed
|
|
31
|
+
|
|
32
|
+
// ─── Constructors ────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @description No-arg constructor for deserialization compatibility
|
|
36
|
+
*/
|
|
37
|
+
public {ClassName}() {
|
|
38
|
+
this.tags = new List<String>();
|
|
39
|
+
this.isActive = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @description Parameterized constructor for convenience
|
|
44
|
+
* @param name The name value
|
|
45
|
+
* @param recordId The associated record Id
|
|
46
|
+
*/
|
|
47
|
+
public {ClassName}(String name, Id recordId) {
|
|
48
|
+
this();
|
|
49
|
+
this.name = name;
|
|
50
|
+
this.recordId = recordId;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── Factory Methods ─────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @description Creates a DTO instance from an SObject record
|
|
57
|
+
* @param record The source {SObject} record
|
|
58
|
+
* @return A populated {ClassName} instance
|
|
59
|
+
*/
|
|
60
|
+
public static {ClassName} fromSObject(SObject record) {
|
|
61
|
+
if (record == null) {
|
|
62
|
+
return new {ClassName}();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
{ClassName} dto = new {ClassName}();
|
|
66
|
+
dto.recordId = record.Id;
|
|
67
|
+
dto.name = (String) record.get('Name');
|
|
68
|
+
// TODO: Map additional fields
|
|
69
|
+
|
|
70
|
+
return dto;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @description Creates a list of DTOs from a list of SObject records
|
|
75
|
+
* @param records The source records
|
|
76
|
+
* @return List of populated {ClassName} instances
|
|
77
|
+
*/
|
|
78
|
+
public static List<{ClassName}> fromSObjects(List<SObject> records) {
|
|
79
|
+
List<{ClassName}> dtos = new List<{ClassName}>();
|
|
80
|
+
if (records == null) {
|
|
81
|
+
return dtos;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (SObject record : records) {
|
|
85
|
+
dtos.add(fromSObject(record));
|
|
86
|
+
}
|
|
87
|
+
return dtos;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Utility Methods ─────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @description Serializes this DTO to a JSON string
|
|
94
|
+
* @return JSON representation of this DTO
|
|
95
|
+
*/
|
|
96
|
+
public String toJson() {
|
|
97
|
+
return JSON.serialize(this);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @description Deserializes a JSON string into a {ClassName} instance
|
|
102
|
+
* @param jsonString The JSON string to deserialize
|
|
103
|
+
* @return A {ClassName} instance
|
|
104
|
+
*/
|
|
105
|
+
public static {ClassName} fromJson(String jsonString) {
|
|
106
|
+
return ({ClassName}) JSON.deserialize(jsonString, {ClassName}.class);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Custom exception for {describe when this exception is thrown}.
|
|
3
|
+
* Use this exception to signal domain-specific errors that callers
|
|
4
|
+
* can catch and handle distinctly from system exceptions.
|
|
5
|
+
* @author Generated by Apex Class Writer Skill
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* throw new {ClassName}('Account merge failed: duplicate detected.');
|
|
9
|
+
*
|
|
10
|
+
* // Wrap a caught exception
|
|
11
|
+
* try {
|
|
12
|
+
* // ... risky operation
|
|
13
|
+
* } catch (DmlException e) {
|
|
14
|
+
* throw new {ClassName}('DML failed during account merge: ' + e.getMessage());
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
public with sharing class {ClassName} extends Exception {
|
|
18
|
+
// Apex custom exceptions automatically inherit:
|
|
19
|
+
// - getMessage()
|
|
20
|
+
// - getCause()
|
|
21
|
+
// - getStackTraceString()
|
|
22
|
+
// - setMessage(String)
|
|
23
|
+
// - initCause(Exception)
|
|
24
|
+
//
|
|
25
|
+
// And support these constructor patterns:
|
|
26
|
+
// - new {ClassName}()
|
|
27
|
+
// - new {ClassName}('message')
|
|
28
|
+
// - new {ClassName}(causeException)
|
|
29
|
+
// - new {ClassName}('message', causeException)
|
|
30
|
+
//
|
|
31
|
+
// Note: Apex does NOT support custom constructors on Exception subclasses.
|
|
32
|
+
// To add context, use the message string or create a wrapper pattern:
|
|
33
|
+
//
|
|
34
|
+
// Example wrapper pattern (if you need structured error data):
|
|
35
|
+
//
|
|
36
|
+
// public class {ClassName}Detail {
|
|
37
|
+
// public String errorCode;
|
|
38
|
+
// public List<Id> failedRecordIds;
|
|
39
|
+
// public String detail;
|
|
40
|
+
//
|
|
41
|
+
// public {ClassName}Detail(String errorCode, List<Id> failedRecordIds, String detail) {
|
|
42
|
+
// this.errorCode = errorCode;
|
|
43
|
+
// this.failedRecordIds = failedRecordIds;
|
|
44
|
+
// this.detail = detail;
|
|
45
|
+
// }
|
|
46
|
+
//
|
|
47
|
+
// public override String toString() {
|
|
48
|
+
// return '[' + errorCode + '] ' + detail + ' (Records: ' + failedRecordIds + ')';
|
|
49
|
+
// }
|
|
50
|
+
// }
|
|
51
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Interface for {describe the capability or contract this interface defines}.
|
|
3
|
+
* Implement this interface to provide {describe what implementations do}.
|
|
4
|
+
* @author Generated by Apex Class Writer Skill
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* public class EmailNotificationService implements {InterfaceName} {
|
|
8
|
+
* public void execute(Map<String, Object> params) {
|
|
9
|
+
* // Implementation
|
|
10
|
+
* }
|
|
11
|
+
* }
|
|
12
|
+
*/
|
|
13
|
+
public interface {InterfaceName} {
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @description {Describe what this method should do}
|
|
17
|
+
* @param params {Describe the parameter}
|
|
18
|
+
* @return {Describe the return value}
|
|
19
|
+
*/
|
|
20
|
+
// TODO: Define interface methods
|
|
21
|
+
// Example:
|
|
22
|
+
// void execute(Map<String, Object> params);
|
|
23
|
+
// Boolean isEligible(SObject record);
|
|
24
|
+
// List<SObject> process(List<SObject> records);
|
|
25
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Queueable Apex class for {describe the async operation}.
|
|
3
|
+
* Accepts data through the constructor for stateful processing.
|
|
4
|
+
* Optionally implements Database.AllowsCallouts for external integrations.
|
|
5
|
+
* @author Generated by Apex Class Writer Skill
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Enqueue the job
|
|
9
|
+
* Id jobId = System.enqueueJob(new {ClassName}(recordIds));
|
|
10
|
+
*/
|
|
11
|
+
public with sharing class {ClassName} implements Queueable /*, Database.AllowsCallouts */ {
|
|
12
|
+
|
|
13
|
+
// ─── Constants ───────────────────────────────────────────────────────
|
|
14
|
+
private static final Integer MAX_CHAIN_DEPTH = 5;
|
|
15
|
+
|
|
16
|
+
// ─── Instance Variables (Stateful) ───────────────────────────────────
|
|
17
|
+
private Set<Id> recordIds;
|
|
18
|
+
private Integer chainDepth;
|
|
19
|
+
|
|
20
|
+
// ─── Constructors ────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @description Creates a new queueable job to process the specified records
|
|
24
|
+
* @param recordIds Set of record Ids to process
|
|
25
|
+
*/
|
|
26
|
+
public {ClassName}(Set<Id> recordIds) {
|
|
27
|
+
this(recordIds, 0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @description Creates a new queueable job with chain depth tracking
|
|
32
|
+
* @param recordIds Set of record Ids to process
|
|
33
|
+
* @param chainDepth Current depth in the queueable chain
|
|
34
|
+
*/
|
|
35
|
+
public {ClassName}(Set<Id> recordIds, Integer chainDepth) {
|
|
36
|
+
this.recordIds = recordIds ?? new Set<Id>();
|
|
37
|
+
this.chainDepth = chainDepth;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Queueable Interface ─────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @description Executes the asynchronous work
|
|
44
|
+
* @param context The queueable context
|
|
45
|
+
*/
|
|
46
|
+
public void execute(QueueableContext context) {
|
|
47
|
+
if (this.recordIds.isEmpty()) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// TODO: Implement the async processing logic
|
|
53
|
+
// List<{SObject}> records = {SObject}Selector.selectByIds(this.recordIds);
|
|
54
|
+
// ... process records ...
|
|
55
|
+
|
|
56
|
+
// Chain to next job if there's more work and we haven't hit the depth limit
|
|
57
|
+
chainIfNeeded();
|
|
58
|
+
|
|
59
|
+
} catch (Exception e) {
|
|
60
|
+
handleError(context.getJobId(), e);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Private Helpers ─────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @description Chains to the next queueable job if needed, with depth guard
|
|
68
|
+
*/
|
|
69
|
+
private void chainIfNeeded() {
|
|
70
|
+
// TODO: Determine if chaining is needed (e.g., remaining records to process)
|
|
71
|
+
Set<Id> remainingIds = new Set<Id>();
|
|
72
|
+
|
|
73
|
+
if (!remainingIds.isEmpty() && this.chainDepth < MAX_CHAIN_DEPTH) {
|
|
74
|
+
if (!Test.isRunningTest()) {
|
|
75
|
+
System.enqueueJob(new {ClassName}(remainingIds, this.chainDepth + 1));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @description Handles errors during execution
|
|
82
|
+
* @param jobId The async job Id
|
|
83
|
+
* @param e The exception that occurred
|
|
84
|
+
*/
|
|
85
|
+
private void handleError(Id jobId, Exception e) {
|
|
86
|
+
System.debug(LoggingLevel.ERROR,
|
|
87
|
+
'{ClassName} failed (Job: ' + jobId + '): ' +
|
|
88
|
+
e.getMessage() + '\n' + e.getStackTraceString()
|
|
89
|
+
);
|
|
90
|
+
// TODO: Persist error to a log object or send notification
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Schedulable Apex class for {describe the scheduled operation}.
|
|
3
|
+
* Delegates heavy processing to a Batch or Queueable job.
|
|
4
|
+
* Keep execute() lightweight — it should only launch other jobs.
|
|
5
|
+
* @author Generated by Apex Class Writer Skill
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Schedule to run daily at 2 AM
|
|
9
|
+
* String jobId = System.schedule(
|
|
10
|
+
* '{ClassName} - Daily',
|
|
11
|
+
* {ClassName}.CRON_DAILY_2AM,
|
|
12
|
+
* new {ClassName}()
|
|
13
|
+
* );
|
|
14
|
+
*
|
|
15
|
+
* // Or use the convenience method
|
|
16
|
+
* String jobId = {ClassName}.scheduleDaily();
|
|
17
|
+
*/
|
|
18
|
+
public with sharing class {ClassName} implements Schedulable {
|
|
19
|
+
|
|
20
|
+
// ─── CRON Expressions ────────────────────────────────────────────────
|
|
21
|
+
// Seconds Minutes Hours Day_of_month Month Day_of_week Optional_year
|
|
22
|
+
|
|
23
|
+
/** @description Runs daily at 2:00 AM */
|
|
24
|
+
public static final String CRON_DAILY_2AM = '0 0 2 * * ?';
|
|
25
|
+
|
|
26
|
+
/** @description Runs every weekday at 6:00 AM */
|
|
27
|
+
public static final String CRON_WEEKDAYS_6AM = '0 0 6 ? * MON-FRI';
|
|
28
|
+
|
|
29
|
+
/** @description Runs hourly at the top of the hour */
|
|
30
|
+
public static final String CRON_HOURLY = '0 0 * * * ?';
|
|
31
|
+
|
|
32
|
+
// ─── Schedulable Interface ───────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @description Entry point for the scheduled execution.
|
|
36
|
+
* Delegates to a Batch or Queueable for the actual work.
|
|
37
|
+
* @param sc The schedulable context
|
|
38
|
+
*/
|
|
39
|
+
public void execute(SchedulableContext sc) {
|
|
40
|
+
// Option A: Launch a Batch job
|
|
41
|
+
// Database.executeBatch(new {BatchClassName}(), 200);
|
|
42
|
+
|
|
43
|
+
// Option B: Launch a Queueable job
|
|
44
|
+
// System.enqueueJob(new {QueueableClassName}(params));
|
|
45
|
+
|
|
46
|
+
// TODO: Implement delegation to appropriate async job
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── Convenience Scheduling Methods ──────────────────────────────────
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @description Schedules this job to run daily at 2 AM
|
|
53
|
+
* @return The scheduled job Id
|
|
54
|
+
*/
|
|
55
|
+
public static String scheduleDaily() {
|
|
56
|
+
return System.schedule(
|
|
57
|
+
'{ClassName} - Daily 2AM',
|
|
58
|
+
CRON_DAILY_2AM,
|
|
59
|
+
new {ClassName}()
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @description Aborts this scheduled job by name
|
|
65
|
+
* @param jobName The name used when scheduling
|
|
66
|
+
*/
|
|
67
|
+
public static void abort(String jobName) {
|
|
68
|
+
for (CronTrigger ct : [
|
|
69
|
+
SELECT Id FROM CronTrigger
|
|
70
|
+
WHERE CronJobDetail.Name = :jobName
|
|
71
|
+
]) {
|
|
72
|
+
System.abortJob(ct.Id);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Selector class for {SObject} queries.
|
|
3
|
+
* Encapsulates all SOQL for {SObject} records.
|
|
4
|
+
* All methods return bulkified results (Lists or Maps).
|
|
5
|
+
* @author Generated by Apex Class Writer Skill
|
|
6
|
+
*/
|
|
7
|
+
public with sharing class {SObject}Selector {
|
|
8
|
+
|
|
9
|
+
// ─── Field Lists ─────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @description Returns the default set of fields to query for {SObject}.
|
|
13
|
+
* Centralizes field references to keep queries DRY.
|
|
14
|
+
* @return Comma-separated field list as a String
|
|
15
|
+
*/
|
|
16
|
+
private static String getDefaultFields() {
|
|
17
|
+
return String.join(
|
|
18
|
+
new List<String>{
|
|
19
|
+
'Id',
|
|
20
|
+
'Name',
|
|
21
|
+
'CreatedDate',
|
|
22
|
+
'LastModifiedDate'
|
|
23
|
+
// TODO: Add additional fields here
|
|
24
|
+
},
|
|
25
|
+
', '
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ─── Query Methods ───────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @description Selects {SObject} records by their Ids
|
|
33
|
+
* @param recordIds Set of {SObject} Ids to query
|
|
34
|
+
* @return List of {SObject} records matching the provided Ids
|
|
35
|
+
* @example
|
|
36
|
+
* Set<Id> ids = new Set<Id>{ '001xx000003DGbY' };
|
|
37
|
+
* List<{SObject}> results = {SObject}Selector.selectByIds(ids);
|
|
38
|
+
*/
|
|
39
|
+
public static List<{SObject}> selectByIds(Set<Id> recordIds) {
|
|
40
|
+
if (recordIds == null || recordIds.isEmpty()) {
|
|
41
|
+
return new List<{SObject}>();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return Database.query(
|
|
45
|
+
'SELECT ' + getDefaultFields() +
|
|
46
|
+
' FROM {SObject}' +
|
|
47
|
+
' WHERE Id IN :recordIds'
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @description Selects {SObject} records as a Map keyed by Id
|
|
53
|
+
* @param recordIds Set of {SObject} Ids to query
|
|
54
|
+
* @return Map of Id to {SObject}
|
|
55
|
+
*/
|
|
56
|
+
public static Map<Id, {SObject}> selectMapByIds(Set<Id> recordIds) {
|
|
57
|
+
return new Map<Id, {SObject}>(selectByIds(recordIds));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @description Selects {SObject} records by a specific field value
|
|
62
|
+
* @param fieldName API name of the field to filter on
|
|
63
|
+
* @param values Set of values to match
|
|
64
|
+
* @return List of matching {SObject} records
|
|
65
|
+
* @example
|
|
66
|
+
* List<{SObject}> results = {SObject}Selector.selectByField('Status__c', new Set<String>{ 'Active' });
|
|
67
|
+
*/
|
|
68
|
+
public static List<{SObject}> selectByField(String fieldName, Set<String> values) {
|
|
69
|
+
if (String.isBlank(fieldName) || values == null || values.isEmpty()) {
|
|
70
|
+
return new List<{SObject}>();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Validate field name to prevent SOQL injection
|
|
74
|
+
Schema.SObjectField field = Schema.SObjectType.{SObject}.fields.getMap().get(fieldName);
|
|
75
|
+
if (field == null) {
|
|
76
|
+
throw new QueryException('Invalid field name: ' + fieldName);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return Database.query(
|
|
80
|
+
'SELECT ' + getDefaultFields() +
|
|
81
|
+
' FROM {SObject}' +
|
|
82
|
+
' WHERE ' + String.escapeSingleQuotes(fieldName) + ' IN :values'
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── Exception ───────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @description Custom exception for query errors
|
|
90
|
+
*/
|
|
91
|
+
public class QueryException extends Exception {}
|
|
92
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Service class for {SObject} business logic.
|
|
3
|
+
* Follows separation of concerns: delegates queries to {SObject}Selector
|
|
4
|
+
* and SObject manipulation to {SObject}Domain where applicable.
|
|
5
|
+
* @author Generated by Apex Class Writer Skill
|
|
6
|
+
*/
|
|
7
|
+
public with sharing class {SObject}Service {
|
|
8
|
+
|
|
9
|
+
// ─── Constants ───────────────────────────────────────────────────────
|
|
10
|
+
private static final String ERROR_NULL_INPUT = 'Input cannot be null or empty.';
|
|
11
|
+
|
|
12
|
+
// ─── Public API ──────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @description {Describe the primary operation}
|
|
16
|
+
* @param recordIds Set of {SObject} Ids to process
|
|
17
|
+
* @return List of processed {SObject} records
|
|
18
|
+
* @throws {SObject}ServiceException if processing fails
|
|
19
|
+
* @example
|
|
20
|
+
* Set<Id> ids = new Set<Id>{ '001xx000003DGbY' };
|
|
21
|
+
* List<{SObject}> results = {SObject}Service.process{SObject}s(ids);
|
|
22
|
+
*/
|
|
23
|
+
public static List<{SObject}> process{SObject}s(Set<Id> recordIds) {
|
|
24
|
+
// Guard clause
|
|
25
|
+
if (recordIds == null || recordIds.isEmpty()) {
|
|
26
|
+
throw new {SObject}ServiceException(ERROR_NULL_INPUT);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Query via Selector
|
|
30
|
+
List<{SObject}> records = {SObject}Selector.selectByIds(recordIds);
|
|
31
|
+
|
|
32
|
+
// Apply business logic
|
|
33
|
+
// TODO: Implement business logic here
|
|
34
|
+
|
|
35
|
+
// DML
|
|
36
|
+
try {
|
|
37
|
+
update records;
|
|
38
|
+
} catch (DmlException e) {
|
|
39
|
+
throw new {SObject}ServiceException(
|
|
40
|
+
'Failed to update {SObject} records: ' + e.getMessage()
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return records;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Convenience Overloads ───────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @description Single-record convenience overload
|
|
51
|
+
* @param recordId The {SObject} Id to process
|
|
52
|
+
* @return The processed {SObject} record
|
|
53
|
+
*/
|
|
54
|
+
public static {SObject} process{SObject}(Id recordId) {
|
|
55
|
+
List<{SObject}> results = process{SObject}s(new Set<Id>{ recordId });
|
|
56
|
+
return results.isEmpty() ? null : results[0];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── Private Helpers ─────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
// TODO: Add private helper methods as needed
|
|
62
|
+
|
|
63
|
+
// ─── Exception ───────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @description Custom exception for {SObject}Service errors
|
|
67
|
+
*/
|
|
68
|
+
public class {SObject}ServiceException extends Exception {}
|
|
69
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Utility class for {describe the category of utilities: String, Date, Collection, etc.}.
|
|
3
|
+
* All methods are static and side-effect-free (no SOQL, no DML).
|
|
4
|
+
* Private constructor prevents instantiation.
|
|
5
|
+
* @author Generated by Apex Class Writer Skill
|
|
6
|
+
*/
|
|
7
|
+
public with sharing class {ClassName} {
|
|
8
|
+
|
|
9
|
+
// ─── Private Constructor ─────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @description Prevents instantiation — use static methods only
|
|
13
|
+
*/
|
|
14
|
+
@TestVisible
|
|
15
|
+
private {ClassName}() {
|
|
16
|
+
// Utility class — do not instantiate
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ─── Public Methods ──────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
// TODO: Add utility methods below. Examples:
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @description Safely converts a String to an Integer, returning a default if parsing fails
|
|
25
|
+
* @param value The String to parse
|
|
26
|
+
* @param defaultValue The fallback value if parsing fails
|
|
27
|
+
* @return The parsed Integer or the default value
|
|
28
|
+
* @example
|
|
29
|
+
* Integer result = {ClassName}.safeParseInteger('42', 0); // returns 42
|
|
30
|
+
* Integer result = {ClassName}.safeParseInteger('abc', 0); // returns 0
|
|
31
|
+
*/
|
|
32
|
+
public static Integer safeParseInteger(String value, Integer defaultValue) {
|
|
33
|
+
if (String.isBlank(value)) {
|
|
34
|
+
return defaultValue;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return Integer.valueOf(value.trim());
|
|
38
|
+
} catch (TypeException e) {
|
|
39
|
+
return defaultValue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @description Chunks a list into smaller sublists of the specified size.
|
|
45
|
+
* Useful for processing records in governor-limit-safe batches.
|
|
46
|
+
* @param items The list to chunk
|
|
47
|
+
* @param chunkSize The maximum size of each chunk
|
|
48
|
+
* @return A list of sublists, each containing up to chunkSize elements
|
|
49
|
+
* @example
|
|
50
|
+
* List<List<String>> chunks = {ClassName}.chunkList(myList, 200);
|
|
51
|
+
*/
|
|
52
|
+
public static List<List<Object>> chunkList(List<Object> items, Integer chunkSize) {
|
|
53
|
+
List<List<Object>> chunks = new List<List<Object>>();
|
|
54
|
+
if (items == null || items.isEmpty() || chunkSize <= 0) {
|
|
55
|
+
return chunks;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
List<Object> currentChunk = new List<Object>();
|
|
59
|
+
for (Object item : items) {
|
|
60
|
+
currentChunk.add(item);
|
|
61
|
+
if (currentChunk.size() == chunkSize) {
|
|
62
|
+
chunks.add(currentChunk);
|
|
63
|
+
currentChunk = new List<Object>();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!currentChunk.isEmpty()) {
|
|
68
|
+
chunks.add(currentChunk);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return chunks;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @description Extracts a Set of non-null field values from a list of SObjects
|
|
76
|
+
* @param records The SObject records to extract from
|
|
77
|
+
* @param fieldName The API name of the field to extract
|
|
78
|
+
* @return A Set of non-null String values
|
|
79
|
+
* @example
|
|
80
|
+
* Set<String> emails = {ClassName}.pluckStrings(contacts, 'Email');
|
|
81
|
+
*/
|
|
82
|
+
public static Set<String> pluckStrings(List<SObject> records, String fieldName) {
|
|
83
|
+
Set<String> values = new Set<String>();
|
|
84
|
+
if (records == null || String.isBlank(fieldName)) {
|
|
85
|
+
return values;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (SObject record : records) {
|
|
89
|
+
Object val = record.get(fieldName);
|
|
90
|
+
if (val != null) {
|
|
91
|
+
values.add(String.valueOf(val));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return values;
|
|
96
|
+
}
|
|
97
|
+
}
|