Compare commits

..

No commits in common. "8510eacecb043b5b448de2c4e8ad34d1fba95f16" and "66be5d83ff51a5e5bfb66b55210e7779384a5b67" have entirely different histories.

10 changed files with 6 additions and 258 deletions

View File

@ -8,13 +8,13 @@
## Tasks
- [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 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 2 — EnvelopeCreateResult class + createEnvelope() method:** Add `EnvelopeCreateResult` inner class and `createEnvelope(Id caseId, String accountCode, String templateId)` to `DocusignESignatureService`. (FR-002)
- [ ] **Task 2 — EnvelopeCreateResult class + createEnvelope() method:** Add `EnvelopeCreateResult` inner class and `createEnvelope(Id caseId, String accountCode, String templateId)` to `DocusignESignatureService`. (FR-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 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 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 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)

View File

@ -22,11 +22,6 @@ 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;
}
@ -318,11 +313,6 @@ 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,
@ -354,11 +344,6 @@ 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) {
@ -448,29 +433,6 @@ 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,

View File

@ -43,14 +43,6 @@ 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);
@ -152,78 +144,6 @@ 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);

View File

@ -147,92 +147,4 @@ 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'));
}
}

View File

@ -1,8 +0,0 @@
<?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>

View File

@ -1,11 +0,0 @@
<?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>

View File

@ -1,11 +0,0 @@
<?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>

View File

@ -1,9 +0,0 @@
<?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>

View File

@ -1,8 +0,0 @@
<?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>

View File

@ -491,7 +491,8 @@ for i in $(seq 1 "$MAX_ITERATIONS"); do
run_agent "$i" build
logfile="$LOG_DIR/iteration-${i}.log"
status=0; check_output "$logfile" || status=$?
check_output "$logfile"
status=$?
case $status in
0)