refactor: extract Request and Result as standalone global classes

- Created DocusignEnvelopeRequest.cls (separate global class for input)
- Created DocusignEnvelopeResult.cls (separate global class for output)
- Updated DocusignCompositeEnvelopeBuilder to use standalone classes
- Updated DocusignEnvelopeRequestHandler to reference standalone classes
- Updated all test classes to use new class references
- Fixes Flow Builder visibility issues with nested inner classes
- Better API design with clear input/output types
- Easier to extend and reuse across other classes
This commit is contained in:
Paul Huliganga 2026-02-25 10:05:36 -05:00
parent 565b851462
commit 7df62e06ca
8 changed files with 115 additions and 103 deletions

View File

@ -15,16 +15,16 @@ global with sharing class DocusignCompositeEnvelopeBuilder {
description='Combines multiple Docusign templates into a single envelope'
category='Docusign'
)
public static List<Result> sendCompositeEnvelope(List<Request> requests) {
List<Result> results = new List<Result>();
public static List<DocusignEnvelopeResult> sendCompositeEnvelope(List<DocusignEnvelopeRequest> requests) {
List<DocusignEnvelopeResult> results = new List<DocusignEnvelopeResult>();
// Process first request (Flow only sends one)
if (requests == null || requests.isEmpty()) {
return buildErrorResult('No request provided');
}
Request req = requests[0];
Result result = new Result();
DocusignEnvelopeRequest req = requests[0];
DocusignEnvelopeResult result = new DocusignEnvelopeResult();
try {
// Validate request using handler
@ -104,67 +104,11 @@ global with sharing class DocusignCompositeEnvelopeBuilder {
* @param errorMessage Error message
* @return List containing single error result
*/
private static List<Result> buildErrorResult(String errorMessage) {
Result result = new Result();
private static List<DocusignEnvelopeResult> buildErrorResult(String errorMessage) {
DocusignEnvelopeResult result = new DocusignEnvelopeResult();
result.success = false;
result.errorMessage = errorMessage;
result.envelopeId = null;
return new List<Result>{ result };
}
/**
* @description Input parameters for invocable method (from Screen Flow)
*/
global class Request {
@InvocableVariable(
label='Template IDs'
description='List of Docusign template IDs to combine'
required=true
)
public List<String> templateIds;
@InvocableVariable(
label='Salesforce Record ID'
description='ID of the Salesforce record to attach documents to'
required=true
)
public String recordId;
@InvocableVariable(
label='Language'
description='Language code (en or es)'
required=false
)
public String language;
@InvocableVariable(
label='Email Subject'
description='Subject line for envelope email'
required=false
)
public String emailSubject;
}
/**
* @description Output parameters for invocable method (to Screen Flow)
*/
global class Result {
@InvocableVariable(
label='Envelope ID'
description='Docusign envelope ID'
)
public String envelopeId;
@InvocableVariable(
label='Success'
description='True if envelope was created successfully'
)
public Boolean success;
@InvocableVariable(
label='Error Message'
description='Error message if envelope creation failed'
)
public String errorMessage;
return new List<DocusignEnvelopeResult>{ result };
}
}

View File

@ -51,7 +51,7 @@ private class DocusignCompositeEnvelopeBuilderTest {
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{'template-1', 'template-2', 'template-3'};
req.recordId = '001000000ABC123';
req.language = 'en';
@ -59,8 +59,8 @@ private class DocusignCompositeEnvelopeBuilderTest {
// Act
Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req});
List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest();
// Assert
@ -81,14 +81,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{'template-1'};
req.recordId = '001000000ABC123';
// Act
Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req});
List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest();
// Assert
@ -111,14 +111,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
templateIds.add('template-' + i);
}
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = templateIds;
req.recordId = '001000000ABC123';
// Act
Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req});
List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest();
// Assert
@ -136,22 +136,20 @@ private class DocusignCompositeEnvelopeBuilderTest {
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{'template-1', 'template-2', 'template-1'}; // Duplicate
req.recordId = '001000000ABC123';
// Act
Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req});
List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest();
// Assert
System.assertEquals(true, results[0].success, 'Should handle duplicates');
}
// Custom fields test removed - not supported in InvocableVariable (Phase 2 enhancement)
/**
* @description Test validation failure - no template IDs
*/
@ -160,14 +158,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
// Arrange
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>(); // Empty
req.recordId = '001000000ABC123';
// Act
Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req});
List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest();
// Assert
@ -188,14 +186,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
templateIds.add('template-' + i);
}
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = templateIds; // 15 templates (> max of 14)
req.recordId = '001000000ABC123';
// Act
Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req});
List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest();
// Assert
@ -211,14 +209,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
// Arrange
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{'template-1'};
req.recordId = ''; // Blank
// Act
Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req});
List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest();
// Assert
@ -237,14 +235,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{'invalid-template'};
req.recordId = '001000000ABC123';
// Act
Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req});
List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest();
// Assert
@ -263,14 +261,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'invalid-token');
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{'template-1'};
req.recordId = '001000000ABC123';
// Act
Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req});
List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest();
// Assert

View File

@ -0,0 +1,35 @@
/**
* @description Input parameters for DocusignCompositeEnvelopeBuilder invocable method
* @author Paul Huliganga
* @date 2026-02-25
*/
global class DocusignEnvelopeRequest {
@InvocableVariable(
label='Template IDs'
description='List of Docusign template IDs to combine'
required=true
)
global List<String> templateIds;
@InvocableVariable(
label='Salesforce Record ID'
description='ID of the Salesforce record to attach documents to'
required=true
)
global String recordId;
@InvocableVariable(
label='Language'
description='Language code (en or es)'
required=false
)
global String language;
@InvocableVariable(
label='Email Subject'
description='Subject line for envelope email'
required=false
)
global String emailSubject;
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>60.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -10,7 +10,7 @@ global with sharing class DocusignEnvelopeRequestHandler {
* @param req Request object to validate
* @throws IllegalArgumentException if validation fails
*/
public static void validateRequest(DocusignCompositeEnvelopeBuilder.Request req) {
public static void validateRequest(DocusignEnvelopeRequest req) {
if (req.templateIds == null || req.templateIds.isEmpty()) {
throw new IllegalArgumentException('At least one template ID is required');
}
@ -36,7 +36,7 @@ global with sharing class DocusignEnvelopeRequestHandler {
* @param req Request object containing parameters
* @return JSON string for Docusign API request
*/
public static String buildEnvelopeJSON(DocusignCompositeEnvelopeBuilder.Request req) {
public static String buildEnvelopeJSON(DocusignEnvelopeRequest req) {
// Remove duplicates and sort alphabetically
List<String> sortedTemplateIds = sortTemplatesAlphabetically(
new Set<String>(req.templateIds)

View File

@ -9,7 +9,7 @@ public class DocusignEnvelopeRequestHandlerTest {
@isTest
static void testValidateRequest_Success() {
// Setup
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{ 'template1', 'template2' };
req.recordId = '001xx000003DHf';
req.language = 'en';
@ -27,7 +27,7 @@ public class DocusignEnvelopeRequestHandlerTest {
@isTest
static void testValidateRequest_NoTemplateIds() {
// Setup
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>();
req.recordId = '001xx000003DHf';
@ -45,7 +45,7 @@ public class DocusignEnvelopeRequestHandlerTest {
@isTest
static void testValidateRequest_TooManyTemplates() {
// Setup
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>();
for (Integer i = 0; i < 15; i++) {
req.templateIds.add('template' + i);
@ -66,7 +66,7 @@ public class DocusignEnvelopeRequestHandlerTest {
@isTest
static void testValidateRequest_NoRecordId() {
// Setup
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{ 'template1' };
req.recordId = '';
@ -84,7 +84,7 @@ public class DocusignEnvelopeRequestHandlerTest {
@isTest
static void testValidateRequest_BlankTemplateId() {
// Setup
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{ 'template1', '', 'template3' };
req.recordId = '001xx000003DHf';
@ -102,7 +102,7 @@ public class DocusignEnvelopeRequestHandlerTest {
@isTest
static void testBuildEnvelopeJSON_Success() {
// Setup
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{ 'template2', 'template1', 'template2' }; // duplicates and unsorted
req.recordId = '001xx000003DHf';
req.language = 'es';
@ -123,7 +123,7 @@ public class DocusignEnvelopeRequestHandlerTest {
@isTest
static void testBuildEnvelopeJSON_DefaultSubject() {
// Setup
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{ 'template1' };
req.recordId = '001xx000003DHf';
req.emailSubject = null;
@ -140,7 +140,7 @@ public class DocusignEnvelopeRequestHandlerTest {
@isTest
static void testBuildEnvelopeJSON_TemplatesAreSorted() {
// Setup
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{ 'template3', 'template1', 'template2' };
req.recordId = '001xx000003DHf';
@ -159,7 +159,7 @@ public class DocusignEnvelopeRequestHandlerTest {
@isTest
static void testBuildEnvelopeJSON_DuplicatesRemoved() {
// Setup
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = new List<String>{ 'template1', 'template1', 'template2' };
req.recordId = '001xx000003DHf';

View File

@ -0,0 +1,25 @@
/**
* @description Output parameters for DocusignCompositeEnvelopeBuilder invocable method
* @author Paul Huliganga
* @date 2026-02-25
*/
global class DocusignEnvelopeResult {
@InvocableVariable(
label='Envelope ID'
description='Docusign envelope ID'
)
global String envelopeId;
@InvocableVariable(
label='Success'
description='True if envelope was created successfully'
)
global Boolean success;
@InvocableVariable(
label='Error Message'
description='Error message if envelope creation failed'
)
global String errorMessage;
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>60.0</apiVersion>
<status>Active</status>
</ApexClass>