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' description='Combines multiple Docusign templates into a single envelope'
category='Docusign' category='Docusign'
) )
public static List<Result> sendCompositeEnvelope(List<Request> requests) { public static List<DocusignEnvelopeResult> sendCompositeEnvelope(List<DocusignEnvelopeRequest> requests) {
List<Result> results = new List<Result>(); List<DocusignEnvelopeResult> results = new List<DocusignEnvelopeResult>();
// Process first request (Flow only sends one) // Process first request (Flow only sends one)
if (requests == null || requests.isEmpty()) { if (requests == null || requests.isEmpty()) {
return buildErrorResult('No request provided'); return buildErrorResult('No request provided');
} }
Request req = requests[0]; DocusignEnvelopeRequest req = requests[0];
Result result = new Result(); DocusignEnvelopeResult result = new DocusignEnvelopeResult();
try { try {
// Validate request using handler // Validate request using handler
@ -104,67 +104,11 @@ global with sharing class DocusignCompositeEnvelopeBuilder {
* @param errorMessage Error message * @param errorMessage Error message
* @return List containing single error result * @return List containing single error result
*/ */
private static List<Result> buildErrorResult(String errorMessage) { private static List<DocusignEnvelopeResult> buildErrorResult(String errorMessage) {
Result result = new Result(); DocusignEnvelopeResult result = new DocusignEnvelopeResult();
result.success = false; result.success = false;
result.errorMessage = errorMessage; result.errorMessage = errorMessage;
result.envelopeId = null; result.envelopeId = null;
return new List<Result>{ result }; return new List<DocusignEnvelopeResult>{ 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;
} }
} }

View File

@ -51,7 +51,7 @@ private class DocusignCompositeEnvelopeBuilderTest {
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); 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.templateIds = new List<String>{'template-1', 'template-2', 'template-3'};
req.recordId = '001000000ABC123'; req.recordId = '001000000ABC123';
req.language = 'en'; req.language = 'en';
@ -59,8 +59,8 @@ private class DocusignCompositeEnvelopeBuilderTest {
// Act // Act
Test.startTest(); Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results = List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req}); DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest(); Test.stopTest();
// Assert // Assert
@ -81,14 +81,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); 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.templateIds = new List<String>{'template-1'};
req.recordId = '001000000ABC123'; req.recordId = '001000000ABC123';
// Act // Act
Test.startTest(); Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results = List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req}); DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest(); Test.stopTest();
// Assert // Assert
@ -111,14 +111,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
templateIds.add('template-' + i); templateIds.add('template-' + i);
} }
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request(); DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = templateIds; req.templateIds = templateIds;
req.recordId = '001000000ABC123'; req.recordId = '001000000ABC123';
// Act // Act
Test.startTest(); Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results = List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req}); DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest(); Test.stopTest();
// Assert // Assert
@ -136,22 +136,20 @@ private class DocusignCompositeEnvelopeBuilderTest {
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); 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.templateIds = new List<String>{'template-1', 'template-2', 'template-1'}; // Duplicate
req.recordId = '001000000ABC123'; req.recordId = '001000000ABC123';
// Act // Act
Test.startTest(); Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results = List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req}); DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest(); Test.stopTest();
// Assert // Assert
System.assertEquals(true, results[0].success, 'Should handle duplicates'); 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 * @description Test validation failure - no template IDs
*/ */
@ -160,14 +158,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
// Arrange // Arrange
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); 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.templateIds = new List<String>(); // Empty
req.recordId = '001000000ABC123'; req.recordId = '001000000ABC123';
// Act // Act
Test.startTest(); Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results = List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req}); DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest(); Test.stopTest();
// Assert // Assert
@ -188,14 +186,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
templateIds.add('template-' + i); templateIds.add('template-' + i);
} }
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request(); DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
req.templateIds = templateIds; // 15 templates (> max of 14) req.templateIds = templateIds; // 15 templates (> max of 14)
req.recordId = '001000000ABC123'; req.recordId = '001000000ABC123';
// Act // Act
Test.startTest(); Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results = List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req}); DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest(); Test.stopTest();
// Assert // Assert
@ -211,14 +209,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
// Arrange // Arrange
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); 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.templateIds = new List<String>{'template-1'};
req.recordId = ''; // Blank req.recordId = ''; // Blank
// Act // Act
Test.startTest(); Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results = List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req}); DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest(); Test.stopTest();
// Assert // Assert
@ -237,14 +235,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); 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.templateIds = new List<String>{'invalid-template'};
req.recordId = '001000000ABC123'; req.recordId = '001000000ABC123';
// Act // Act
Test.startTest(); Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results = List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req}); DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest(); Test.stopTest();
// Assert // Assert
@ -263,14 +261,14 @@ private class DocusignCompositeEnvelopeBuilderTest {
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'invalid-token'); 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.templateIds = new List<String>{'template-1'};
req.recordId = '001000000ABC123'; req.recordId = '001000000ABC123';
// Act // Act
Test.startTest(); Test.startTest();
List<DocusignCompositeEnvelopeBuilder.Result> results = List<DocusignEnvelopeResult> results =
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignCompositeEnvelopeBuilder.Request>{req}); DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List<DocusignEnvelopeRequest>{req});
Test.stopTest(); Test.stopTest();
// Assert // 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 * @param req Request object to validate
* @throws IllegalArgumentException if validation fails * @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()) { if (req.templateIds == null || req.templateIds.isEmpty()) {
throw new IllegalArgumentException('At least one template ID is required'); 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 * @param req Request object containing parameters
* @return JSON string for Docusign API request * @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 // Remove duplicates and sort alphabetically
List<String> sortedTemplateIds = sortTemplatesAlphabetically( List<String> sortedTemplateIds = sortTemplatesAlphabetically(
new Set<String>(req.templateIds) new Set<String>(req.templateIds)

View File

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