diff --git a/composite-envelope-builder/force-app/main/default/classes/DocusignCompositeEnvelopeBuilder.cls b/composite-envelope-builder/force-app/main/default/classes/DocusignCompositeEnvelopeBuilder.cls index 405c814..95d00d5 100644 --- a/composite-envelope-builder/force-app/main/default/classes/DocusignCompositeEnvelopeBuilder.cls +++ b/composite-envelope-builder/force-app/main/default/classes/DocusignCompositeEnvelopeBuilder.cls @@ -19,6 +19,15 @@ global with sharing class DocusignCompositeEnvelopeBuilder { // Role names must match EXACTLY what's configured in the Docusign templates private static final String ROLE_SERVICE_COORDINATOR = 'Service Coordinator'; private static final String ROLE_DOCUSIGN_RECIPIENT = 'Docusign Recipient #1'; + + // ============================================================ + // MULTI-COPY TEMPLATE: Update this if the template name changes. + // This is matched against the dfsle__EnvelopeConfiguration__c Name + // field using a case-insensitive contains check. + // Both English and Spanish versions share this base name. + // ============================================================ + @TestVisible + private static final String MULTI_COPY_TEMPLATE_NAME = 'Authorization to Release Information'; @InvocableMethod( label='Send Composite Docusign Envelope' @@ -44,8 +53,38 @@ global with sharing class DocusignCompositeEnvelopeBuilder { new dfsle.Entity(req.recordId) ); - // Build document list from templates (deduplicated and sorted) - List sortedTemplateIds = new List(new Set(req.templateIds)); + // Expand multi-copy templates before deduplication. + // If the user selected the Authorization to Release Information template and + // requested more than 1 copy, insert additional copies of its template ID into + // the list now so the deduplication step handles all IDs uniformly. + List expandedTemplateIds = new List(req.templateIds); + Integer copies = (req.authReleaseFormCopies != null && req.authReleaseFormCopies > 1) + ? Math.min(req.authReleaseFormCopies, 3) + : 1; + if (copies > 1) { + // Find which template ID(s) correspond to the multi-copy template + List multiCopyIds = new List(); + for (dfsle__EnvelopeConfiguration__c config : [ + SELECT dfsle__DocuSignId__c + FROM dfsle__EnvelopeConfiguration__c + WHERE dfsle__DocuSignId__c IN :req.templateIds + AND Name LIKE :('%' + MULTI_COPY_TEMPLATE_NAME + '%') + ]) { + multiCopyIds.add(config.dfsle__DocuSignId__c); + } + // Add (copies - 1) additional entries for each matched template ID + for (String multiId : multiCopyIds) { + for (Integer i = 1; i < copies; i++) { + expandedTemplateIds.add(multiId); + } + } + } + + // Build document list from templates. + // NOTE: We intentionally do NOT deduplicate here so that multiple copies of + // the same template ID produce distinct documents in the envelope. + // We sort by label after resolving names instead. + List sortedTemplateIds = new List(expandedTemplateIds); sortedTemplateIds.sort(); // Query template names for document labels (shows in Docusign Status) @@ -65,7 +104,10 @@ global with sharing class DocusignCompositeEnvelopeBuilder { List documents = new List(); List docNames = new List(); - Map labelToId = new Map(); + // Use a list of label+id pairs to correctly handle duplicate template IDs + // (e.g. multiple copies of Authorization to Release Information) + List labelIdPairs = new List(); + Map labelCounters = new Map(); for (String templateId : sortedTemplateIds) { String label; if (templateShortNames.containsKey(templateId)) { @@ -75,10 +117,24 @@ global with sharing class DocusignCompositeEnvelopeBuilder { } else { label = templateId; } - labelToId.put(label, templateId); + // If the same label appears more than once, append " (Copy N)" to distinguish + if (labelCounters.containsKey(label)) { + Integer count = labelCounters.get(label) + 1; + labelCounters.put(label, count); + label = label + ' (Copy ' + count + ')'; + } else { + labelCounters.put(label, 1); + } + labelIdPairs.add(new String[]{ label, templateId }); docNames.add(label); } + // Sort by label for consistent ordering docNames.sort(); + // Re-order labelIdPairs to match sorted docNames + Map labelToId = new Map(); + for (String[] pair : labelIdPairs) { + labelToId.put(pair[0], pair[1]); + } List sortedIds = new List(); for (String label : docNames) { sortedIds.add(labelToId.get(label)); @@ -95,9 +151,30 @@ global with sharing class DocusignCompositeEnvelopeBuilder { } myEnvelope = myEnvelope.withDocuments(documents); + // Build a deduplicated display list for the email subject and body. + // Where a template appears more than once (multi-copy), show the base label + // once with a " [x N]" count suffix, e.g. "Authorization to Release Information [x 3]". + // This keeps the subject and body clean while the envelope still contains all copies. + Map baseNameCounts = new Map(); + List baseNameOrder = new List(); + for (String label : docNames) { + // Strip the " (Copy N)" suffix to recover the base label + String baseName = label.replaceAll(' \\(Copy \\d+\\)$', ''); + if (!baseNameCounts.containsKey(baseName)) { + baseNameCounts.put(baseName, 0); + baseNameOrder.add(baseName); + } + baseNameCounts.put(baseName, baseNameCounts.get(baseName) + 1); + } + List displayNames = new List(); + for (String baseName : baseNameOrder) { + Integer cnt = baseNameCounts.get(baseName); + displayNames.add(cnt > 1 ? baseName + ' (' + cnt + ')' : baseName); + } + // Set combined template names as the envelope document name // (shows in Docusign Status "Document Name" column) - String combinedName = String.join(docNames, ', '); + String combinedName = String.join(displayNames, ', '); if (combinedName.length() > 255) { combinedName = combinedName.left(252) + '...'; } @@ -114,22 +191,27 @@ global with sharing class DocusignCompositeEnvelopeBuilder { List recipients = resolveRecipients(req.recordId); myEnvelope = myEnvelope.withRecipients(recipients); - // Set envelope subject to combined template names and body to concatenated template email messages - // Query for EmailMessage__c + // Set envelope subject to combined display names (deduplicated, with copy counts). + // Query for EmailMessage__c — use a deduplicated set of template IDs so each + // template's body text is included only once even when multi-copy is in effect. + Set uniqueTemplateIds = new Set(sortedIds); Map templateBodies = new Map(); for (dfsle__EnvelopeConfiguration__c config : [ SELECT dfsle__DocuSignId__c, dfsle__EmailMessage__c FROM dfsle__EnvelopeConfiguration__c - WHERE dfsle__DocuSignId__c IN :sortedTemplateIds + WHERE dfsle__DocuSignId__c IN :uniqueTemplateIds ]) { if (String.isNotBlank(config.dfsle__EmailMessage__c)) { templateBodies.put(config.dfsle__DocuSignId__c, config.dfsle__EmailMessage__c); } } + // Build body using one entry per unique template ID (preserving sorted order) + Set bodyIdsAdded = new Set(); List bodyParts = new List(); for (String templateId : sortedIds) { - if (templateBodies.containsKey(templateId)) { + if (templateBodies.containsKey(templateId) && !bodyIdsAdded.contains(templateId)) { bodyParts.add(templateBodies.get(templateId)); + bodyIdsAdded.add(templateId); } } String envelopeSubject = combinedName; @@ -148,7 +230,7 @@ global with sharing class DocusignCompositeEnvelopeBuilder { result.success = true; result.errorMessage = null; - logResult(sortedTemplateIds.size(), result.envelopeId, 'Success (' + String.join(docNames, ', ') + ')', null); + logResult(sortedTemplateIds.size(), result.envelopeId, 'Success (' + String.join(displayNames, ', ') + ')', null); } catch (Exception e) { result.success = false; diff --git a/composite-envelope-builder/force-app/main/default/classes/DocusignEnvelopeRequest.cls b/composite-envelope-builder/force-app/main/default/classes/DocusignEnvelopeRequest.cls index 9e83e96..84c074d 100644 --- a/composite-envelope-builder/force-app/main/default/classes/DocusignEnvelopeRequest.cls +++ b/composite-envelope-builder/force-app/main/default/classes/DocusignEnvelopeRequest.cls @@ -32,4 +32,11 @@ global class DocusignEnvelopeRequest { required=false ) global String emailSubject; + + @InvocableVariable( + label='Authorization to Release Form Copies' + description='Number of times to include the Authorization to Release Information template (1-3). Only used when that template is selected.' + required=false + ) + global Integer authReleaseFormCopies; }