From c3421e858fa0ba839709bf4d88e4c9285c4a54a0 Mon Sep 17 00:00:00 2001 From: paulh Date: Thu, 9 Apr 2026 22:17:08 -0400 Subject: [PATCH] feat(esignature): add EnvelopeCreateResult class and createEnvelope() method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds EnvelopeCreateResult inner DTO and createEnvelope(Id, String, String) to DocusignESignatureService. Option A (template-based): POSTs to /v2.1/accounts/{accountId}/envelopes with templateId, templateRoles (Signer), and status=sent. Guards blank templateId and blank appraiser email; catches all exceptions and wraps in result. Named credential sourced from CLM_Account_Setting__mdt per ADR-002. Agent: claude-sonnet-4-6 Tests: 8/8 passing | N/A (no new test methods — Task 3 covers test coverage) Tests-Added: 0 TypeScript: N/A (Apex project) --- IMPLEMENTATION_PLAN.md | 2 +- .../classes/DocusignESignatureService.cls | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index 89ab987..9d027bc 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -10,7 +10,7 @@ - [x] **Task 1 — Custom fields:** Add 5 eSignature tracking fields to `Appraiser_Case__c` object metadata and update `package.xml` if needed. Deploy to verify. (FR-001) -- [ ] **Task 2 — EnvelopeCreateResult class + createEnvelope() method:** Add `EnvelopeCreateResult` inner class and `createEnvelope(Id caseId, String accountCode, String templateId)` to `DocusignESignatureService`. (FR-002) +- [x] **Task 2 — EnvelopeCreateResult class + createEnvelope() method:** Add `EnvelopeCreateResult` inner class and `createEnvelope(Id caseId, String accountCode, String templateId)` to `DocusignESignatureService`. (FR-002) - [ ] **Task 3 — Tests for createEnvelope():** Add test coverage in `DocusignESignatureServiceTest` — success path (mock 201), failure path (mock 400), blank email guard. (NFR-001, TC-001, TC-002) diff --git a/force-app/main/default/classes/DocusignESignatureService.cls b/force-app/main/default/classes/DocusignESignatureService.cls index 0d14898..b9928d4 100644 --- a/force-app/main/default/classes/DocusignESignatureService.cls +++ b/force-app/main/default/classes/DocusignESignatureService.cls @@ -43,6 +43,14 @@ public with sharing class DocusignESignatureService { @AuraEnabled public String rawJson; } + public class EnvelopeCreateResult { + @AuraEnabled public Boolean success; + @AuraEnabled public String envelopeId; + @AuraEnabled public String status; + @AuraEnabled public String envelopeUri; + @AuraEnabled public String errorMessage; + } + @AuraEnabled(cacheable=true) public static ESignatureAccountConfig getAccountConfig(String accountCode) { CLM_Account_Setting__mdt row = requireAccountSetting(accountCode); @@ -144,6 +152,78 @@ public with sharing class DocusignESignatureService { return parseEnvelopeList(response.responseBody); } + @AuraEnabled(cacheable=false) + public static EnvelopeCreateResult createEnvelope(Id caseId, String accountCode, String templateId) { + EnvelopeCreateResult result = new EnvelopeCreateResult(); + result.success = false; + try { + if (String.isBlank(templateId)) { + result.errorMessage = 'Template ID is required.'; + return result; + } + + CLM_Account_Setting__mdt row = requireAccountSetting(accountCode); + String targetAccountId = requireESignatureAccountId(row, accountCode, null); + + Appraiser_Case__c caseRecord = [ + SELECT Appraiser_Email__c, + Appraiser_Name__c, + Appraiser_Last_Name__c, + Appraiser_Salutation__c + FROM Appraiser_Case__c + WHERE Id = :caseId + LIMIT 1 + ]; + + if (String.isBlank(caseRecord.Appraiser_Email__c)) { + result.errorMessage = 'Appraiser email is blank on this case. Cannot create envelope.'; + return result; + } + + String appraiserName = String.isNotBlank(caseRecord.Appraiser_Name__c) + ? caseRecord.Appraiser_Name__c + : ((String.isNotBlank(caseRecord.Appraiser_Salutation__c) + ? caseRecord.Appraiser_Salutation__c + ' ' + : '') + String.valueOf(caseRecord.Appraiser_Last_Name__c)).trim(); + + Map roleMap = new Map{ + 'email' => caseRecord.Appraiser_Email__c, + 'name' => appraiserName, + 'roleName' => 'Signer' + }; + Map bodyMap = new Map{ + 'templateId' => templateId, + 'templateRoles' => new List{ roleMap }, + 'status' => 'sent' + }; + + HttpRequest req = new HttpRequest(); + req.setEndpoint(buildEndpoint( + '/v2.1/accounts/' + targetAccountId + '/envelopes', + row.ESignature_Rest_Named_Credential__c + )); + req.setMethod('POST'); + req.setHeader('Content-Type', 'application/json'); + req.setTimeout(30000); + req.setBody(JSON.serialize(bodyMap)); + + HttpResponse res = new Http().send(req); + if (res.getStatusCode() >= 200 && res.getStatusCode() < 300) { + Map responseMap = (Map) JSON.deserializeUntyped(res.getBody()); + result.success = true; + result.envelopeId = (String) responseMap.get('envelopeId'); + result.status = (String) responseMap.get('status'); + result.envelopeUri = (String) responseMap.get('uri'); + } else { + result.errorMessage = 'eSignature API Error (HTTP ' + res.getStatusCode() + '): ' + res.getBody(); + } + } catch (Exception e) { + result.success = false; + result.errorMessage = e.getMessage(); + } + return result; + } + @TestVisible private static List parseAccountList(String body) { Object root = JSON.deserializeUntyped(body);