@salesforce/afv-skills 1.5.1 → 1.5.3
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/README.md +16 -416
- package/package.json +5 -3
- package/skills/building-ui-bundle-app/SKILL.md +325 -0
- package/skills/building-ui-bundle-frontend/SKILL.md +122 -0
- package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/component.md +1 -1
- package/skills/creating-b2b-commerce-store/SKILL.md +169 -0
- package/skills/creating-b2b-commerce-store/references/store-vs-storefront.md +169 -0
- package/skills/deploying-ui-bundle/SKILL.md +77 -0
- package/skills/generating-apex/CREDITS.md +30 -0
- package/skills/generating-apex/SKILL.md +342 -189
- package/skills/generating-apex/assets/abstract.cls +12 -9
- package/skills/generating-apex/assets/batch.cls +7 -8
- package/skills/generating-apex/assets/domain.cls +5 -6
- package/skills/generating-apex/assets/dto.cls +11 -12
- package/skills/generating-apex/assets/exception.cls +1 -2
- package/skills/generating-apex/assets/interface.cls +2 -3
- package/skills/generating-apex/assets/invocable.cls +114 -0
- package/skills/generating-apex/assets/queueable.cls +6 -7
- package/skills/generating-apex/assets/rest-resource.cls +300 -0
- package/skills/generating-apex/assets/schedulable.cls +7 -8
- package/skills/generating-apex/assets/selector.cls +7 -8
- package/skills/generating-apex/assets/service.cls +4 -5
- package/skills/generating-apex/assets/trigger.cls +45 -0
- package/skills/generating-apex/assets/utility.cls +5 -6
- package/skills/generating-apex/references/AccountDeduplicationBatch.cls +7 -8
- package/skills/generating-apex/references/AccountSelector.cls +10 -11
- package/skills/generating-apex/references/AccountService.cls +9 -10
- package/skills/generating-apex-test/CREDITS.md +30 -0
- package/skills/generating-apex-test/SKILL.md +165 -74
- package/skills/generating-apex-test/assets/test-class-template.cls +25 -56
- package/skills/generating-apex-test/assets/test-data-factory-template.cls +0 -1
- package/skills/generating-apex-test/references/assertion-patterns.md +38 -95
- package/skills/generating-apex-test/references/async-testing.md +59 -142
- package/skills/generating-apex-test/references/mocking-patterns.md +77 -76
- package/skills/generating-apex-test/references/test-data-factory.md +29 -130
- package/skills/generating-experience-react-site/SKILL.md +9 -9
- package/skills/generating-experience-react-site/docs/configure-metadata-digital-experience.md +1 -1
- package/skills/generating-flexipage/SKILL.md +28 -12
- package/skills/generating-ui-bundle-features/SKILL.md +45 -0
- package/skills/generating-ui-bundle-metadata/SKILL.md +106 -0
- package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/SKILL.md +5 -5
- package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/constraints.md +2 -2
- package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/examples.md +1 -1
- package/skills/{implementing-webapp-file-upload → implementing-ui-bundle-file-upload}/SKILL.md +11 -11
- package/skills/searching-media/SKILL.md +1 -1
- package/skills/uplifting-components-to-slds2/SKILL.md +236 -0
- package/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md +438 -0
- package/skills/uplifting-components-to-slds2/references/common-patterns.md +87 -0
- package/skills/uplifting-components-to-slds2/references/examples.md +443 -0
- package/skills/uplifting-components-to-slds2/references/migration-checklist.md +67 -0
- package/skills/uplifting-components-to-slds2/references/non-color-hooks-decision-guide.md +333 -0
- package/skills/uplifting-components-to-slds2/references/rule-lwc-token-to-slds-hook.md +135 -0
- package/skills/uplifting-components-to-slds2/references/rule-no-deprecated-tokens-slds1.md +211 -0
- package/skills/uplifting-components-to-slds2/references/rule-no-hardcoded-values.md +160 -0
- package/skills/uplifting-components-to-slds2/references/rule-no-slds-class-overrides.md +126 -0
- package/skills/{using-webapp-salesforce-data → using-ui-bundle-salesforce-data}/SKILL.md +52 -25
- package/skills/using-ui-bundle-salesforce-data/references/mutation-query-generation.md +140 -0
- package/skills/using-ui-bundle-salesforce-data/references/query-testing.md +78 -0
- package/skills/using-ui-bundle-salesforce-data/references/read-query-generation.md +307 -0
- package/skills/using-ui-bundle-salesforce-data/references/schema-introspection.md +53 -0
- package/skills/using-ui-bundle-salesforce-data/references/ui-bundle-integration.md +221 -0
- package/skills/{using-webapp-salesforce-data → using-ui-bundle-salesforce-data/scripts}/graphql-search.sh +75 -23
- package/skills/building-webapp-data-visualization/SKILL.md +0 -72
- package/skills/building-webapp-data-visualization/implementation/bar-line-chart.md +0 -316
- package/skills/building-webapp-data-visualization/implementation/dashboard-layout.md +0 -189
- package/skills/building-webapp-data-visualization/implementation/donut-chart.md +0 -181
- package/skills/building-webapp-data-visualization/implementation/stat-card.md +0 -150
- package/skills/building-webapp-react-components/SKILL.md +0 -96
- package/skills/configuring-webapp-csp-trusted-sites/SKILL.md +0 -90
- package/skills/configuring-webapp-metadata/SKILL.md +0 -158
- package/skills/creating-webapp/SKILL.md +0 -138
- package/skills/deploying-webapp-to-salesforce/SKILL.md +0 -226
- package/skills/installing-webapp-features/SKILL.md +0 -210
- /package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/header-footer.md +0 -0
- /package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/page.md +0 -0
- /package/skills/{configuring-webapp-csp-trusted-sites/implementation/metadata-format.md → generating-ui-bundle-metadata/implementation/csp-metadata-format.md} +0 -0
- /package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/style-tokens.md +0 -0
- /package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/troubleshooting.md +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST API endpoint for {SObject} operations.
|
|
3
|
+
* Exposes CRUD operations via @RestResource at /services/apexrest/{urlPath}/v1/*.
|
|
4
|
+
* Uses versioned URL mapping for future-proof API evolution.
|
|
5
|
+
* All queries use WITH USER_MODE for CRUD/FLS enforcement.
|
|
6
|
+
*
|
|
7
|
+
* Authentication: OAuth 2.0 via Connected App
|
|
8
|
+
* Base URL: /services/apexrest/{urlPath}/v1/
|
|
9
|
+
*/
|
|
10
|
+
@RestResource(urlMapping='/{urlPath}/v1/*')
|
|
11
|
+
global with sharing class {ClassName} {
|
|
12
|
+
|
|
13
|
+
// ─── Constants ────────────────────────────────────────────────────────
|
|
14
|
+
private static final Integer DEFAULT_PAGE_SIZE = 20;
|
|
15
|
+
private static final Integer MAX_PAGE_SIZE = 200;
|
|
16
|
+
private static final String ERROR_MISSING_ID = 'Record Id is required in the URL path.';
|
|
17
|
+
private static final String ERROR_INVALID_ID = 'Invalid Salesforce Id format.';
|
|
18
|
+
private static final String ERROR_MISSING_BODY = 'Request body is required.';
|
|
19
|
+
private static final String ERROR_NOT_FOUND = '{SObject} record not found.';
|
|
20
|
+
private static final String ERROR_INSUFFICIENT_ACCESS = 'Insufficient access to perform this operation.';
|
|
21
|
+
|
|
22
|
+
// ─── GET — Retrieve ───────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Retrieves a single {SObject} by Id (URL path) or a paginated list (query params).
|
|
26
|
+
* Single: GET /services/apexrest/{urlPath}/v1/{recordId}
|
|
27
|
+
* List: GET /services/apexrest/{urlPath}/v1?pageSize=20&offset=0
|
|
28
|
+
* @return ApiResponse containing the requested data
|
|
29
|
+
*/
|
|
30
|
+
@HttpGet
|
|
31
|
+
global static ApiResponse doGet() {
|
|
32
|
+
RestRequest req = RestContext.request;
|
|
33
|
+
RestResponse res = RestContext.response;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
String recordId = extractIdFromUri(req.requestURI);
|
|
37
|
+
|
|
38
|
+
if (String.isNotBlank(recordId)) {
|
|
39
|
+
return getSingleRecord(recordId, res);
|
|
40
|
+
}
|
|
41
|
+
return getRecordList(req, res);
|
|
42
|
+
|
|
43
|
+
} catch (Exception e) {
|
|
44
|
+
return handleError(res, 500, e.getMessage());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── POST — Create ───────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Creates a new {SObject} record from the JSON request body.
|
|
52
|
+
* POST /services/apexrest/{urlPath}/v1/
|
|
53
|
+
* @return ApiResponse with the created record Id
|
|
54
|
+
*/
|
|
55
|
+
@HttpPost
|
|
56
|
+
global static ApiResponse doPost() {
|
|
57
|
+
RestRequest req = RestContext.request;
|
|
58
|
+
RestResponse res = RestContext.response;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
if (req.requestBody == null || String.isBlank(req.requestBody.toString())) {
|
|
62
|
+
return handleError(res, 400, ERROR_MISSING_BODY);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
{ClassName}Request payload = ({ClassName}Request) JSON.deserialize(
|
|
66
|
+
req.requestBody.toString(),
|
|
67
|
+
{ClassName}Request.class
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// TODO: Map request payload to SObject fields
|
|
71
|
+
{SObject} record = new {SObject}(
|
|
72
|
+
Name = payload.name
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
Database.SaveResult saveResult = Database.insert(record, true);
|
|
76
|
+
if (saveResult.isSuccess()) {
|
|
77
|
+
res.statusCode = 201;
|
|
78
|
+
return new ApiResponse(true, 'Record created successfully.', record.Id);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return handleError(res, 422, saveResult.getErrors()[0].getMessage());
|
|
82
|
+
|
|
83
|
+
} catch (JSONException e) {
|
|
84
|
+
return handleError(res, 400, 'Malformed JSON: ' + e.getMessage());
|
|
85
|
+
} catch (DmlException e) {
|
|
86
|
+
return handleError(res, 422, 'Validation failed: ' + e.getDmlMessage(0));
|
|
87
|
+
} catch (Exception e) {
|
|
88
|
+
return handleError(res, 500, e.getMessage());
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ─── PATCH — Partial Update ───────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Partially updates an existing {SObject} record.
|
|
96
|
+
* PATCH /services/apexrest/{urlPath}/v1/{recordId}
|
|
97
|
+
* @return ApiResponse confirming the update
|
|
98
|
+
*/
|
|
99
|
+
@HttpPatch
|
|
100
|
+
global static ApiResponse doPatch() {
|
|
101
|
+
RestRequest req = RestContext.request;
|
|
102
|
+
RestResponse res = RestContext.response;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
String recordId = extractIdFromUri(req.requestURI);
|
|
106
|
+
if (String.isBlank(recordId)) {
|
|
107
|
+
return handleError(res, 400, ERROR_MISSING_ID);
|
|
108
|
+
}
|
|
109
|
+
if (!isValidSalesforceId(recordId)) {
|
|
110
|
+
return handleError(res, 400, ERROR_INVALID_ID);
|
|
111
|
+
}
|
|
112
|
+
if (req.requestBody == null || String.isBlank(req.requestBody.toString())) {
|
|
113
|
+
return handleError(res, 400, ERROR_MISSING_BODY);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
List<{SObject}> existing = [
|
|
117
|
+
SELECT Id FROM {SObject} WHERE Id = :recordId WITH USER_MODE LIMIT 1
|
|
118
|
+
];
|
|
119
|
+
if (existing.isEmpty()) {
|
|
120
|
+
return handleError(res, 404, ERROR_NOT_FOUND);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
Map<String, Object> fieldUpdates = (Map<String, Object>) JSON.deserializeUntyped(
|
|
124
|
+
req.requestBody.toString()
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
{SObject} record = existing[0];
|
|
128
|
+
// TODO: Apply allowed field updates from the payload to the record
|
|
129
|
+
// for (String fieldName : fieldUpdates.keySet()) {
|
|
130
|
+
// record.put(fieldName, fieldUpdates.get(fieldName));
|
|
131
|
+
// }
|
|
132
|
+
|
|
133
|
+
Database.SaveResult saveResult = Database.update(record, true);
|
|
134
|
+
if (saveResult.isSuccess()) {
|
|
135
|
+
res.statusCode = 200;
|
|
136
|
+
return new ApiResponse(true, 'Record updated successfully.', record.Id);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return handleError(res, 422, saveResult.getErrors()[0].getMessage());
|
|
140
|
+
|
|
141
|
+
} catch (JSONException e) {
|
|
142
|
+
return handleError(res, 400, 'Malformed JSON: ' + e.getMessage());
|
|
143
|
+
} catch (DmlException e) {
|
|
144
|
+
return handleError(res, 422, 'Validation failed: ' + e.getDmlMessage(0));
|
|
145
|
+
} catch (Exception e) {
|
|
146
|
+
return handleError(res, 500, e.getMessage());
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ─── DELETE — Remove ──────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Deletes a {SObject} record by Id.
|
|
154
|
+
* DELETE /services/apexrest/{urlPath}/v1/{recordId}
|
|
155
|
+
* @return ApiResponse confirming the deletion
|
|
156
|
+
*/
|
|
157
|
+
@HttpDelete
|
|
158
|
+
global static ApiResponse doDelete() {
|
|
159
|
+
RestRequest req = RestContext.request;
|
|
160
|
+
RestResponse res = RestContext.response;
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
String recordId = extractIdFromUri(req.requestURI);
|
|
164
|
+
if (String.isBlank(recordId)) {
|
|
165
|
+
return handleError(res, 400, ERROR_MISSING_ID);
|
|
166
|
+
}
|
|
167
|
+
if (!isValidSalesforceId(recordId)) {
|
|
168
|
+
return handleError(res, 400, ERROR_INVALID_ID);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
List<{SObject}> existing = [
|
|
172
|
+
SELECT Id FROM {SObject} WHERE Id = :recordId WITH USER_MODE LIMIT 1
|
|
173
|
+
];
|
|
174
|
+
if (existing.isEmpty()) {
|
|
175
|
+
return handleError(res, 404, ERROR_NOT_FOUND);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
Database.DeleteResult deleteResult = Database.delete(existing[0], true);
|
|
179
|
+
if (deleteResult.isSuccess()) {
|
|
180
|
+
res.statusCode = 200;
|
|
181
|
+
return new ApiResponse(true, 'Record deleted successfully.', recordId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return handleError(res, 422, deleteResult.getErrors()[0].getMessage());
|
|
185
|
+
|
|
186
|
+
} catch (DmlException e) {
|
|
187
|
+
return handleError(res, 422, e.getDmlMessage(0));
|
|
188
|
+
} catch (Exception e) {
|
|
189
|
+
return handleError(res, 500, e.getMessage());
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ─── Private Helpers ──────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
private static ApiResponse getSingleRecord(String recordId, RestResponse res) {
|
|
196
|
+
if (!isValidSalesforceId(recordId)) {
|
|
197
|
+
return handleError(res, 400, ERROR_INVALID_ID);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
List<{SObject}> records = [
|
|
201
|
+
SELECT Id, Name, CreatedDate, LastModifiedDate
|
|
202
|
+
FROM {SObject}
|
|
203
|
+
WHERE Id = :recordId
|
|
204
|
+
WITH USER_MODE
|
|
205
|
+
LIMIT 1
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
if (records.isEmpty()) {
|
|
209
|
+
return handleError(res, 404, ERROR_NOT_FOUND);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
res.statusCode = 200;
|
|
213
|
+
ApiResponse response = new ApiResponse(true, 'Record retrieved successfully.', recordId);
|
|
214
|
+
response.data = records[0];
|
|
215
|
+
return response;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private static ApiResponse getRecordList(RestRequest req, RestResponse res) {
|
|
219
|
+
Integer pageSize = getIntParam(req, 'pageSize', DEFAULT_PAGE_SIZE);
|
|
220
|
+
Integer offset = getIntParam(req, 'offset', 0);
|
|
221
|
+
|
|
222
|
+
pageSize = Math.min(pageSize, MAX_PAGE_SIZE);
|
|
223
|
+
|
|
224
|
+
List<{SObject}> records = [
|
|
225
|
+
SELECT Id, Name, CreatedDate, LastModifiedDate
|
|
226
|
+
FROM {SObject}
|
|
227
|
+
WITH USER_MODE
|
|
228
|
+
ORDER BY Name ASC
|
|
229
|
+
LIMIT :pageSize
|
|
230
|
+
OFFSET :offset
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
res.statusCode = 200;
|
|
234
|
+
ApiResponse response = new ApiResponse(true, 'Records retrieved successfully.', null);
|
|
235
|
+
response.records = records;
|
|
236
|
+
response.pageSize = pageSize;
|
|
237
|
+
response.offset = offset;
|
|
238
|
+
return response;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private static String extractIdFromUri(String uri) {
|
|
242
|
+
String lastSegment = uri.substringAfterLast('/');
|
|
243
|
+
if (String.isBlank(lastSegment) || lastSegment == 'v1') {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
return lastSegment;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private static Boolean isValidSalesforceId(String idValue) {
|
|
250
|
+
return Pattern.matches('[a-zA-Z0-9]{15,18}', idValue);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private static Integer getIntParam(RestRequest req, String paramName, Integer defaultValue) {
|
|
254
|
+
String paramValue = req.params.get(paramName);
|
|
255
|
+
if (String.isBlank(paramValue)) {
|
|
256
|
+
return defaultValue;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
return Integer.valueOf(paramValue);
|
|
260
|
+
} catch (TypeException e) {
|
|
261
|
+
return defaultValue;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private static ApiResponse handleError(RestResponse res, Integer statusCode, String message) {
|
|
266
|
+
res.statusCode = statusCode;
|
|
267
|
+
return new ApiResponse(false, message, null);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ─── Request / Response DTOs ──────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Inbound request payload for POST operations.
|
|
274
|
+
* Extend with additional fields as needed.
|
|
275
|
+
*/
|
|
276
|
+
global class {ClassName}Request {
|
|
277
|
+
global String name;
|
|
278
|
+
// TODO: Add fields matching the expected JSON request body
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Standardized API response envelope.
|
|
283
|
+
* All endpoints return this structure for consistent client parsing.
|
|
284
|
+
*/
|
|
285
|
+
global class ApiResponse {
|
|
286
|
+
global Boolean success;
|
|
287
|
+
global String message;
|
|
288
|
+
global String recordId;
|
|
289
|
+
global SObject data;
|
|
290
|
+
global List<SObject> records;
|
|
291
|
+
global Integer pageSize;
|
|
292
|
+
global Integer offset;
|
|
293
|
+
|
|
294
|
+
global ApiResponse(Boolean success, String message, String recordId) {
|
|
295
|
+
this.success = success;
|
|
296
|
+
this.message = message;
|
|
297
|
+
this.recordId = recordId;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Schedulable Apex class for {describe the scheduled operation}.
|
|
3
3
|
* Delegates heavy processing to a Batch or Queueable job.
|
|
4
4
|
* Keep execute() lightweight — it should only launch other jobs.
|
|
5
|
-
* @author Generated by Apex Class Writer Skill
|
|
6
5
|
*
|
|
7
6
|
* @example
|
|
8
7
|
* // Schedule to run daily at 2 AM
|
|
@@ -20,19 +19,19 @@ public with sharing class {ClassName} implements Schedulable {
|
|
|
20
19
|
// ─── CRON Expressions ────────────────────────────────────────────────
|
|
21
20
|
// Seconds Minutes Hours Day_of_month Month Day_of_week Optional_year
|
|
22
21
|
|
|
23
|
-
/**
|
|
22
|
+
/** Runs daily at 2:00 AM */
|
|
24
23
|
public static final String CRON_DAILY_2AM = '0 0 2 * * ?';
|
|
25
24
|
|
|
26
|
-
/**
|
|
25
|
+
/** Runs every weekday at 6:00 AM */
|
|
27
26
|
public static final String CRON_WEEKDAYS_6AM = '0 0 6 ? * MON-FRI';
|
|
28
27
|
|
|
29
|
-
/**
|
|
28
|
+
/** Runs hourly at the top of the hour */
|
|
30
29
|
public static final String CRON_HOURLY = '0 0 * * * ?';
|
|
31
30
|
|
|
32
31
|
// ─── Schedulable Interface ───────────────────────────────────────────
|
|
33
32
|
|
|
34
33
|
/**
|
|
35
|
-
*
|
|
34
|
+
* Entry point for the scheduled execution.
|
|
36
35
|
* Delegates to a Batch or Queueable for the actual work.
|
|
37
36
|
* @param sc The schedulable context
|
|
38
37
|
*/
|
|
@@ -49,7 +48,7 @@ public with sharing class {ClassName} implements Schedulable {
|
|
|
49
48
|
// ─── Convenience Scheduling Methods ──────────────────────────────────
|
|
50
49
|
|
|
51
50
|
/**
|
|
52
|
-
*
|
|
51
|
+
* Schedules this job to run daily at 2 AM
|
|
53
52
|
* @return The scheduled job Id
|
|
54
53
|
*/
|
|
55
54
|
public static String scheduleDaily() {
|
|
@@ -61,7 +60,7 @@ public with sharing class {ClassName} implements Schedulable {
|
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
/**
|
|
64
|
-
*
|
|
63
|
+
* Aborts this scheduled job by name
|
|
65
64
|
* @param jobName The name used when scheduling
|
|
66
65
|
*/
|
|
67
66
|
public static void abort(String jobName) {
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Selector class for {SObject} queries.
|
|
3
3
|
* Encapsulates all SOQL for {SObject} records.
|
|
4
4
|
* All methods return bulkified results (Lists or Maps).
|
|
5
|
-
* @author Generated by Apex Class Writer Skill
|
|
6
5
|
*/
|
|
7
|
-
public
|
|
6
|
+
public inherited sharing class {SObject}Selector {
|
|
8
7
|
|
|
9
8
|
// ─── Field Lists ─────────────────────────────────────────────────────
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
11
|
+
* Returns the default set of fields to query for {SObject}.
|
|
13
12
|
* Centralizes field references to keep queries DRY.
|
|
14
13
|
* @return Comma-separated field list as a String
|
|
15
14
|
*/
|
|
@@ -29,7 +28,7 @@ public with sharing class {SObject}Selector {
|
|
|
29
28
|
// ─── Query Methods ───────────────────────────────────────────────────
|
|
30
29
|
|
|
31
30
|
/**
|
|
32
|
-
*
|
|
31
|
+
* Selects {SObject} records by their Ids
|
|
33
32
|
* @param recordIds Set of {SObject} Ids to query
|
|
34
33
|
* @return List of {SObject} records matching the provided Ids
|
|
35
34
|
* @example
|
|
@@ -49,7 +48,7 @@ public with sharing class {SObject}Selector {
|
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
/**
|
|
52
|
-
*
|
|
51
|
+
* Selects {SObject} records as a Map keyed by Id
|
|
53
52
|
* @param recordIds Set of {SObject} Ids to query
|
|
54
53
|
* @return Map of Id to {SObject}
|
|
55
54
|
*/
|
|
@@ -58,7 +57,7 @@ public with sharing class {SObject}Selector {
|
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
/**
|
|
61
|
-
*
|
|
60
|
+
* Selects {SObject} records by a specific field value
|
|
62
61
|
* @param fieldName API name of the field to filter on
|
|
63
62
|
* @param values Set of values to match
|
|
64
63
|
* @return List of matching {SObject} records
|
|
@@ -86,7 +85,7 @@ public with sharing class {SObject}Selector {
|
|
|
86
85
|
// ─── Exception ───────────────────────────────────────────────────────
|
|
87
86
|
|
|
88
87
|
/**
|
|
89
|
-
*
|
|
88
|
+
* Custom exception for query errors
|
|
90
89
|
*/
|
|
91
90
|
public class QueryException extends Exception {}
|
|
92
91
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Service class for {SObject} business logic.
|
|
3
3
|
* Follows separation of concerns: delegates queries to {SObject}Selector
|
|
4
4
|
* and SObject manipulation to {SObject}Domain where applicable.
|
|
5
|
-
* @author Generated by Apex Class Writer Skill
|
|
6
5
|
*/
|
|
7
6
|
public with sharing class {SObject}Service {
|
|
8
7
|
|
|
@@ -12,7 +11,7 @@ public with sharing class {SObject}Service {
|
|
|
12
11
|
// ─── Public API ──────────────────────────────────────────────────────
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
14
|
+
* {Describe the primary operation}
|
|
16
15
|
* @param recordIds Set of {SObject} Ids to process
|
|
17
16
|
* @return List of processed {SObject} records
|
|
18
17
|
* @throws {SObject}ServiceException if processing fails
|
|
@@ -47,7 +46,7 @@ public with sharing class {SObject}Service {
|
|
|
47
46
|
// ─── Convenience Overloads ───────────────────────────────────────────
|
|
48
47
|
|
|
49
48
|
/**
|
|
50
|
-
*
|
|
49
|
+
* Single-record convenience overload
|
|
51
50
|
* @param recordId The {SObject} Id to process
|
|
52
51
|
* @return The processed {SObject} record
|
|
53
52
|
*/
|
|
@@ -63,7 +62,7 @@ public with sharing class {SObject}Service {
|
|
|
63
62
|
// ─── Exception ───────────────────────────────────────────────────────
|
|
64
63
|
|
|
65
64
|
/**
|
|
66
|
-
*
|
|
65
|
+
* Custom exception for {SObject}Service errors
|
|
67
66
|
*/
|
|
68
67
|
public class {SObject}ServiceException extends Exception {}
|
|
69
68
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {SObject} Trigger
|
|
3
|
+
*/
|
|
4
|
+
trigger {SObject}Trigger on {SObject} (
|
|
5
|
+
before insert,
|
|
6
|
+
before update,
|
|
7
|
+
before delete,
|
|
8
|
+
after insert,
|
|
9
|
+
after update,
|
|
10
|
+
after delete,
|
|
11
|
+
after undelete
|
|
12
|
+
) {
|
|
13
|
+
// ─── Option A: Trigger Actions Framework (TAF) ───────────────────────
|
|
14
|
+
// Delegates to Trigger_Action__mdt-registered action classes.
|
|
15
|
+
// Each action class handles one concern in one context.
|
|
16
|
+
//
|
|
17
|
+
// new MetadataTriggerHandler().run();
|
|
18
|
+
|
|
19
|
+
// ─── Option B: Custom Handler Pattern ────────────────────────────────
|
|
20
|
+
// Delegates to a single handler class that routes by context.
|
|
21
|
+
//
|
|
22
|
+
// {SObject}TriggerHandler handler = new {SObject}TriggerHandler();
|
|
23
|
+
//
|
|
24
|
+
// if (Trigger.isBefore) {
|
|
25
|
+
// if (Trigger.isInsert) {
|
|
26
|
+
// handler.beforeInsert(Trigger.new);
|
|
27
|
+
// } else if (Trigger.isUpdate) {
|
|
28
|
+
// handler.beforeUpdate(Trigger.new, Trigger.oldMap);
|
|
29
|
+
// } else if (Trigger.isDelete) {
|
|
30
|
+
// handler.beforeDelete(Trigger.old, Trigger.oldMap);
|
|
31
|
+
// }
|
|
32
|
+
// } else if (Trigger.isAfter) {
|
|
33
|
+
// if (Trigger.isInsert) {
|
|
34
|
+
// handler.afterInsert(Trigger.new, Trigger.newMap);
|
|
35
|
+
// } else if (Trigger.isUpdate) {
|
|
36
|
+
// handler.afterUpdate(Trigger.new, Trigger.oldMap);
|
|
37
|
+
// } else if (Trigger.isDelete) {
|
|
38
|
+
// handler.afterDelete(Trigger.old, Trigger.oldMap);
|
|
39
|
+
// } else if (Trigger.isUndelete) {
|
|
40
|
+
// handler.afterUndelete(Trigger.new, Trigger.newMap);
|
|
41
|
+
// }
|
|
42
|
+
// }
|
|
43
|
+
|
|
44
|
+
// TODO: Uncomment one option above and remove the other
|
|
45
|
+
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Utility class for {describe the category of utilities: String, Date, Collection, etc.}.
|
|
3
3
|
* All methods are static and side-effect-free (no SOQL, no DML).
|
|
4
4
|
* Private constructor prevents instantiation.
|
|
5
|
-
* @author Generated by Apex Class Writer Skill
|
|
6
5
|
*/
|
|
7
6
|
public with sharing class {ClassName} {
|
|
8
7
|
|
|
9
8
|
// ─── Private Constructor ─────────────────────────────────────────────
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
11
|
+
* Prevents instantiation — use static methods only
|
|
13
12
|
*/
|
|
14
13
|
@TestVisible
|
|
15
14
|
private {ClassName}() {
|
|
@@ -21,7 +20,7 @@ public with sharing class {ClassName} {
|
|
|
21
20
|
// TODO: Add utility methods below. Examples:
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
|
-
*
|
|
23
|
+
* Safely converts a String to an Integer, returning a default if parsing fails
|
|
25
24
|
* @param value The String to parse
|
|
26
25
|
* @param defaultValue The fallback value if parsing fails
|
|
27
26
|
* @return The parsed Integer or the default value
|
|
@@ -41,7 +40,7 @@ public with sharing class {ClassName} {
|
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
/**
|
|
44
|
-
*
|
|
43
|
+
* Chunks a list into smaller sublists of the specified size.
|
|
45
44
|
* Useful for processing records in governor-limit-safe batches.
|
|
46
45
|
* @param items The list to chunk
|
|
47
46
|
* @param chunkSize The maximum size of each chunk
|
|
@@ -72,7 +71,7 @@ public with sharing class {ClassName} {
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
/**
|
|
75
|
-
*
|
|
74
|
+
* Extracts a Set of non-null field values from a list of SObjects
|
|
76
75
|
* @param records The SObject records to extract from
|
|
77
76
|
* @param fieldName The API name of the field to extract
|
|
78
77
|
* @return A Set of non-null String values
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Batch Apex class for identifying and flagging duplicate Account records.
|
|
3
3
|
* Compares Accounts by Name and BillingPostalCode to find potential duplicates.
|
|
4
4
|
* Flags duplicates by setting the Is_Potential_Duplicate__c checkbox.
|
|
5
5
|
* Implements Database.Stateful to track results across batch chunks.
|
|
6
|
-
* @author Generated by Apex Class Writer Skill
|
|
7
6
|
*
|
|
8
7
|
* @example
|
|
9
8
|
* // Run with default batch size (200)
|
|
@@ -26,7 +25,7 @@ public with sharing class AccountDeduplicationBatch implements Database.Batchabl
|
|
|
26
25
|
// ─── Batchable Interface ─────────────────────────────────────────────
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
|
-
*
|
|
28
|
+
* Queries all active Accounts that haven't already been flagged
|
|
30
29
|
* @param bc The batch context
|
|
31
30
|
* @return QueryLocator scoped to unflagged active Accounts
|
|
32
31
|
*/
|
|
@@ -41,7 +40,7 @@ public with sharing class AccountDeduplicationBatch implements Database.Batchabl
|
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
/**
|
|
44
|
-
*
|
|
43
|
+
* Processes each batch by building a duplicate key and checking for matches.
|
|
45
44
|
* Uses a composite key of normalized Name + BillingPostalCode.
|
|
46
45
|
* @param bc The batch context
|
|
47
46
|
* @param scope List of Account records in the current batch
|
|
@@ -82,7 +81,7 @@ public with sharing class AccountDeduplicationBatch implements Database.Batchabl
|
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
/**
|
|
85
|
-
*
|
|
84
|
+
* Logs a summary of the deduplication batch run
|
|
86
85
|
* @param bc The batch context
|
|
87
86
|
*/
|
|
88
87
|
public void finish(Database.BatchableContext bc) {
|
|
@@ -101,7 +100,7 @@ public with sharing class AccountDeduplicationBatch implements Database.Batchabl
|
|
|
101
100
|
// ─── Private Helpers ─────────────────────────────────────────────────
|
|
102
101
|
|
|
103
102
|
/**
|
|
104
|
-
*
|
|
103
|
+
* Builds a normalized composite key for duplicate detection
|
|
105
104
|
* @param acct The Account record
|
|
106
105
|
* @return Normalized key string, or null if insufficient data
|
|
107
106
|
*/
|
|
@@ -117,7 +116,7 @@ public with sharing class AccountDeduplicationBatch implements Database.Batchabl
|
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
/**
|
|
120
|
-
*
|
|
119
|
+
* Processes DML results, tracking successes and failures
|
|
121
120
|
* @param results List of Database.SaveResult from update operation
|
|
122
121
|
*/
|
|
123
122
|
private void processResults(List<Database.SaveResult> results) {
|
|
@@ -139,7 +138,7 @@ public with sharing class AccountDeduplicationBatch implements Database.Batchabl
|
|
|
139
138
|
// ─── Static Helpers ──────────────────────────────────────────────────
|
|
140
139
|
|
|
141
140
|
/**
|
|
142
|
-
*
|
|
141
|
+
* Convenience method to execute with default batch size
|
|
143
142
|
* @return The batch job Id
|
|
144
143
|
*/
|
|
145
144
|
public static Id run() {
|