Designs Salesforce Apex approval processes, multi-step workflows, and Flow-integrated submissions. Covers architecture, criteria, actions, and Apex submission code for discounts, expenses, contracts.
npx claudepluginhub jiten-singh-shahi/salesforce-claude-code --plugin salesforce-claude-codeThis skill uses the workspace's default tool permissions.
@../_reference/APPROVAL_PROCESSES.md
Guides Salesforce Flow design and review: selects flow types (Record-Triggered, Screen, etc.), validates bulk safety (no DML/Get Records in loops), ensures fault handling and automation density checks.
Provides patterns for Salesforce platform development: Lightning Web Components (LWC), Apex triggers/classes, REST/Bulk APIs, Connected Apps, Salesforce DX with scratch orgs and 2GP.
Writes and debugs Apex code, builds Lightning Web Components, optimizes SOQL queries, implements triggers, batch jobs, platform events, and Salesforce integrations. Use for CRM workflows, governor limits, bulk processing, and Salesforce DX CI/CD.
Share bugs, ideas, or general feedback.
@../_reference/APPROVAL_PROCESSES.md
┌─────────────────────────────────────────────────────┐
│ Approval Process │
├─────────────────────────────────────────────────────┤
│ Entry Criteria │ WHO can submit? WHEN? │
│ Initial Submitter │ Record owner, specific users │
├─────────────────────────────────────────────────────┤
│ Step 1: Manager │ Approver: Manager field │
│ Step 2: VP │ Approver: Related user field │
│ Step 3: Finance │ Approver: Queue │
├─────────────────────────────────────────────────────┤
│ Initial Actions │ Lock record, set Status │
│ Approval Actions │ Unlock, update field, email │
│ Rejection Actions │ Unlock, set Status to Rejected │
│ Recall Actions │ Unlock, clear approval fields │
└─────────────────────────────────────────────────────┘
Amount__c > 10000 AND Status__c = 'Draft'
RecordType.Name = 'Enterprise' AND Discount__c > 20
Formula:
AND(Amount__c > 10000, ISPICKVAL(Status__c, 'Draft'), NOT(ISBLANK(OwnerId)))
| Property | Options |
|---|---|
| Approver | User field, Manager field, Queue, Related user |
| Criteria | All records OR filter criteria (step-specific) |
| Reject behavior | Final rejection OR go to previous step |
| Unanimity | All must approve OR first response |
Status__c = "Pending Approval"Status__c = "Approved"Status__c = "Rejected"public class ApprovalService {
public static void submitForApproval(Id recordId, String comments) {
Approval.ProcessSubmitRequest request = new Approval.ProcessSubmitRequest();
request.setObjectId(recordId);
request.setSubmitterId(UserInfo.getUserId());
request.setComments(comments);
Approval.ProcessResult result = Approval.process(request);
if (result.isSuccess()) {
System.debug('Submitted. Instance ID: ' + result.getInstanceId());
} else {
for (Database.Error err : result.getErrors()) {
System.debug(LoggingLevel.ERROR, 'Failed: ' + err.getMessage());
}
}
}
}
public static void approveRecord(Id workItemId, String comments) {
Approval.ProcessWorkitemRequest request = new Approval.ProcessWorkitemRequest();
request.setWorkitemId(workItemId);
request.setAction('Approve');
request.setComments(comments);
Approval.ProcessResult result = Approval.process(request);
if (!result.isSuccess()) {
throw new ApprovalException('Approval failed: ' + result.getErrors());
}
}
public static void rejectRecord(Id workItemId, String comments) {
Approval.ProcessWorkitemRequest request = new Approval.ProcessWorkitemRequest();
request.setWorkitemId(workItemId);
request.setAction('Reject');
request.setComments(comments);
Approval.ProcessResult result = Approval.process(request);
if (!result.isSuccess()) {
throw new ApprovalException('Rejection failed: ' + result.getErrors());
}
}
// Pending work items for current user
List<ProcessInstanceWorkitem> pendingItems = [
SELECT Id, ProcessInstance.TargetObjectId, ProcessInstance.Status,
ProcessInstance.TargetObject.Name, CreatedDate
FROM ProcessInstanceWorkitem
WHERE ActorId = :UserInfo.getUserId()
ORDER BY CreatedDate DESC
];
// Approval history for a record
List<ProcessInstanceStep> history = [
SELECT StepStatus, Comments, Actor.Name, CreatedDate
FROM ProcessInstanceStep
WHERE ProcessInstance.TargetObjectId = :recordId
ORDER BY CreatedDate ASC
];
// Active work item for a specific record
ProcessInstanceWorkitem workItem = [
SELECT Id FROM ProcessInstanceWorkitem
WHERE ProcessInstance.TargetObjectId = :recordId
AND ProcessInstance.Status = 'Pending'
LIMIT 1
];
Use the Submit for Approval action element: specify the record ID, optionally the approval process name, submitter, and comments.
Screen Flows can include an Integrated Approval component for in-flow approve/reject:
Screen Flow:
1. Show Record Details
2. Integrated Approval Component (history, Approve/Reject buttons, comments)
3. Decision: Check outcome
4. Update Records based on outcome
Autolaunched Flow (invoked by Approval Process):
1. Get Records: record's Region__c and Amount__c
2. Decision: Route by amount and region
- > 500K → VP Finance
- > 100K → Regional Manager
- Otherwise → Direct Manager
3. Return approver User ID
public static List<Map<String, Object>> getApprovalHistory(Id recordId) {
List<Map<String, Object>> trail = new List<Map<String, Object>>();
for (ProcessInstance pi : [
SELECT Id, Status, CreatedDate,
(SELECT StepStatus, Comments, Actor.Name, CreatedDate
FROM StepsAndWorkitems ORDER BY CreatedDate)
FROM ProcessInstance
WHERE TargetObjectId = :recordId
ORDER BY CreatedDate DESC
]) {
for (ProcessInstanceHistory step : pi.StepsAndWorkitems) {
trail.add(new Map<String, Object>{
'status' => step.StepStatus,
'approver' => step.Actor.Name,
'comments' => step.Comments,
'date' => step.CreatedDate
});
}
}
return trail;
}
These tests require an approval process to be deployed in the org's metadata. Approval processes cannot be created programmatically in tests.
@isTest
static void testApprovalSubmission() {
Account testAccount = new Account(Name = 'Test Account', AnnualRevenue = 50000);
insert testAccount;
Test.startTest();
Approval.ProcessSubmitRequest request = new Approval.ProcessSubmitRequest();
request.setObjectId(testAccount.Id);
request.setSubmitterId(UserInfo.getUserId());
Approval.ProcessResult result = Approval.process(request);
Test.stopTest();
System.assert(result.isSuccess(), 'Approval submission should succeed');
System.assertEquals('Pending', result.getInstanceStatus());
}
@isTest
static void testApprovalProcess() {
Account testAccount = new Account(Name = 'Test', AnnualRevenue = 50000);
insert testAccount;
Approval.ProcessSubmitRequest submitReq = new Approval.ProcessSubmitRequest();
submitReq.setObjectId(testAccount.Id);
Approval.ProcessResult submitResult = Approval.process(submitReq);
Id workItemId = submitResult.getNewWorkitemIds()[0];
Approval.ProcessWorkitemRequest approveReq = new Approval.ProcessWorkitemRequest();
approveReq.setWorkitemId(workItemId);
approveReq.setAction('Approve');
approveReq.setComments('Looks good');
Test.startTest();
Approval.ProcessResult approveResult = Approval.process(approveReq);
Test.stopTest();
System.assert(approveResult.isSuccess());
System.assertEquals('Approved', approveResult.getInstanceStatus());
}
approvalProcesses/
Discount_Approval.approvalProcess-meta.xml
Include in package.xml:
<types>
<members>Opportunity.Discount_Approval</members>
<name>ApprovalProcess</name>
</types>
Approval processes reference users, queues, and email templates. Ensure dependencies exist in the target org before deployment.
sf-apex-constraints — Governor limits and Apex safety rules