Compare commits
5 Commits
66be5d83ff
...
8510eacecb
| Author | SHA1 | Date |
|---|---|---|
|
|
8510eacecb | |
|
|
207358fa7d | |
|
|
6fe75d8ab3 | |
|
|
c3421e858f | |
|
|
e4b7456dc1 |
|
|
@ -8,13 +8,13 @@
|
|||
|
||||
## Tasks
|
||||
|
||||
- [ ] **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)
|
||||
- [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)
|
||||
- [x] **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)
|
||||
|
||||
- [ ] **Task 4 — persistEnvelopeResult() + CaseContext extension:** Add `persistEnvelopeResult()` to `CLMAdminService`. Extend `CaseContext` inner class and `getCaseContext()` SOQL to include the 5 eSignature fields. (FR-003, FR-004)
|
||||
- [x] **Task 4 — persistEnvelopeResult() + CaseContext extension:** Add `persistEnvelopeResult()` to `CLMAdminService`. Extend `CaseContext` inner class and `getCaseContext()` SOQL to include the 5 eSignature fields. (FR-003, FR-004)
|
||||
|
||||
- [ ] **Task 5 — Tests for persistEnvelopeResult():** Add test coverage in `CLMAdminServiceTest` — verify fields written correctly, `ESignature_Sent_At__c` is set, `ESignature_Completed_At__c` is NOT written on a "sent" result. (NFR-001, TC-003)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ public with sharing class CLMAdminService {
|
|||
@AuraEnabled public String attachedFileUrl;
|
||||
@AuraEnabled public Datetime lastDocGenRequestedAt;
|
||||
@AuraEnabled public Datetime lastDocGenCompletedAt;
|
||||
@AuraEnabled public String eSignatureEnvelopeId;
|
||||
@AuraEnabled public String eSignatureEnvelopeStatus;
|
||||
@AuraEnabled public String eSignatureEnvelopeUrl;
|
||||
@AuraEnabled public Datetime eSignatureSentAt;
|
||||
@AuraEnabled public Datetime eSignatureCompletedAt;
|
||||
@AuraEnabled public List<CaseDeficiencyItem> deficiencies;
|
||||
}
|
||||
|
||||
|
|
@ -313,6 +318,11 @@ public with sharing class CLMAdminService {
|
|||
Attached_File_Url__c,
|
||||
Last_DocGen_Requested_At__c,
|
||||
Last_DocGen_Completed_At__c,
|
||||
ESignature_Envelope_Id__c,
|
||||
ESignature_Envelope_Status__c,
|
||||
ESignature_Sent_At__c,
|
||||
ESignature_Completed_At__c,
|
||||
ESignature_Envelope_Url__c,
|
||||
(SELECT Id,
|
||||
Deficiency_Number__c,
|
||||
Description__c,
|
||||
|
|
@ -344,6 +354,11 @@ public with sharing class CLMAdminService {
|
|||
context.attachedFileUrl = appraiserCase.Attached_File_Url__c;
|
||||
context.lastDocGenRequestedAt = appraiserCase.Last_DocGen_Requested_At__c;
|
||||
context.lastDocGenCompletedAt = appraiserCase.Last_DocGen_Completed_At__c;
|
||||
context.eSignatureEnvelopeId = appraiserCase.ESignature_Envelope_Id__c;
|
||||
context.eSignatureEnvelopeStatus = appraiserCase.ESignature_Envelope_Status__c;
|
||||
context.eSignatureEnvelopeUrl = appraiserCase.ESignature_Envelope_Url__c;
|
||||
context.eSignatureSentAt = appraiserCase.ESignature_Sent_At__c;
|
||||
context.eSignatureCompletedAt = appraiserCase.ESignature_Completed_At__c;
|
||||
context.deficiencies = new List<CaseDeficiencyItem>();
|
||||
|
||||
if (appraiserCase.Deficiencies__r != null) {
|
||||
|
|
@ -433,6 +448,29 @@ public with sharing class CLMAdminService {
|
|||
return result;
|
||||
}
|
||||
|
||||
@AuraEnabled(cacheable=false)
|
||||
public static void persistEnvelopeResult(
|
||||
Id caseId,
|
||||
String envelopeId,
|
||||
String envelopeStatus,
|
||||
String envelopeUri
|
||||
) {
|
||||
if (caseId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Appraiser_Case__c updateCase = new Appraiser_Case__c(Id = caseId);
|
||||
updateCase.ESignature_Envelope_Id__c = envelopeId;
|
||||
updateCase.ESignature_Envelope_Status__c = envelopeStatus;
|
||||
updateCase.ESignature_Sent_At__c = System.now();
|
||||
|
||||
if (String.isNotBlank(envelopeUri)) {
|
||||
updateCase.ESignature_Envelope_Url__c = envelopeUri;
|
||||
}
|
||||
|
||||
update updateCase;
|
||||
}
|
||||
|
||||
private static String formatAddress(Appraiser_Case__c appraiserCase) {
|
||||
return AppraiserCasePayloadBuilder.formatMailingAddress(
|
||||
appraiserCase.Property_Street__c,
|
||||
|
|
|
|||
|
|
@ -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<String, Object> roleMap = new Map<String, Object>{
|
||||
'email' => caseRecord.Appraiser_Email__c,
|
||||
'name' => appraiserName,
|
||||
'roleName' => 'Signer'
|
||||
};
|
||||
Map<String, Object> bodyMap = new Map<String, Object>{
|
||||
'templateId' => templateId,
|
||||
'templateRoles' => new List<Object>{ 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<String, Object> responseMap = (Map<String, Object>) 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<ESignatureAccountSummary> parseAccountList(String body) {
|
||||
Object root = JSON.deserializeUntyped(body);
|
||||
|
|
|
|||
|
|
@ -147,4 +147,92 @@ private class DocusignESignatureServiceTest {
|
|||
DocusignESignatureService.buildEndpoint('/v2.1/accounts', 'Esignature_Demo_NamedCreds')
|
||||
);
|
||||
}
|
||||
|
||||
private class CreateEnvelopeSuccessMock implements HttpCalloutMock {
|
||||
public HttpResponse respond(HttpRequest req) {
|
||||
HttpResponse res = new HttpResponse();
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setStatusCode(201);
|
||||
res.setBody('{"envelopeId":"abc-123","status":"sent","uri":"/v2.1/accounts/12345678/envelopes/abc-123"}');
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateEnvelopeFailureMock implements HttpCalloutMock {
|
||||
public HttpResponse respond(HttpRequest req) {
|
||||
HttpResponse res = new HttpResponse();
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setStatusCode(400);
|
||||
res.setBody('{"errorCode":"TEMPLATE_NOT_IN_ACCOUNT","message":"The template specified is not in the account."}');
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void createEnvelopeReturnsSuccessResultOnHttp201() {
|
||||
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
|
||||
Appraiser_Field_Review_Date__c = Date.today(),
|
||||
Appraiser_Email__c = 'appraiser@example.com',
|
||||
Appraiser_Name__c = 'Jamie Carter'
|
||||
);
|
||||
insert appraiserCase;
|
||||
|
||||
Test.setMock(HttpCalloutMock.class, new CreateEnvelopeSuccessMock());
|
||||
|
||||
Test.startTest();
|
||||
DocusignESignatureService.EnvelopeCreateResult result = DocusignESignatureService.createEnvelope(
|
||||
appraiserCase.Id,
|
||||
'DTC_CLM_Demo',
|
||||
'tmpl-001'
|
||||
);
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(true, result.success);
|
||||
System.assertEquals('abc-123', result.envelopeId);
|
||||
System.assertEquals('sent', result.status);
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void createEnvelopeReturnsFailureResultOnHttp400() {
|
||||
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
|
||||
Appraiser_Field_Review_Date__c = Date.today(),
|
||||
Appraiser_Email__c = 'appraiser@example.com',
|
||||
Appraiser_Name__c = 'Jamie Carter'
|
||||
);
|
||||
insert appraiserCase;
|
||||
|
||||
Test.setMock(HttpCalloutMock.class, new CreateEnvelopeFailureMock());
|
||||
|
||||
Test.startTest();
|
||||
DocusignESignatureService.EnvelopeCreateResult result = DocusignESignatureService.createEnvelope(
|
||||
appraiserCase.Id,
|
||||
'DTC_CLM_Demo',
|
||||
'tmpl-bad'
|
||||
);
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(false, result.success);
|
||||
System.assertNotEquals(null, result.errorMessage);
|
||||
System.assert(result.errorMessage.contains('400'));
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void createEnvelopeReturnsEarlyWhenEmailIsBlank() {
|
||||
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
|
||||
Appraiser_Field_Review_Date__c = Date.today()
|
||||
);
|
||||
insert appraiserCase;
|
||||
|
||||
Test.startTest();
|
||||
DocusignESignatureService.EnvelopeCreateResult result = DocusignESignatureService.createEnvelope(
|
||||
appraiserCase.Id,
|
||||
'DTC_CLM_Demo',
|
||||
'tmpl-001'
|
||||
);
|
||||
Test.stopTest();
|
||||
|
||||
System.assertEquals(false, result.success);
|
||||
System.assertNotEquals(null, result.errorMessage);
|
||||
System.assert(result.errorMessage.toLowerCase().contains('email'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>ESignature_Completed_At__c</fullName>
|
||||
<label>eSignature Completed At</label>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>DateTime</type>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>ESignature_Envelope_Id__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>eSignature Envelope ID</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>ESignature_Envelope_Status__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>eSignature Envelope Status</label>
|
||||
<length>50</length>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>ESignature_Envelope_Url__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<label>eSignature Envelope URL</label>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>Url</type>
|
||||
</CustomField>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>ESignature_Sent_At__c</fullName>
|
||||
<label>eSignature Sent At</label>
|
||||
<required>false</required>
|
||||
<trackHistory>false</trackHistory>
|
||||
<type>DateTime</type>
|
||||
</CustomField>
|
||||
|
|
@ -491,8 +491,7 @@ for i in $(seq 1 "$MAX_ITERATIONS"); do
|
|||
run_agent "$i" build
|
||||
logfile="$LOG_DIR/iteration-${i}.log"
|
||||
|
||||
check_output "$logfile"
|
||||
status=$?
|
||||
status=0; check_output "$logfile" || status=$?
|
||||
|
||||
case $status in
|
||||
0)
|
||||
|
|
|
|||
Loading…
Reference in New Issue