Created
April 24, 2020 04:18
-
-
Save ryanfollmer/431e89b96d46444b0f2ecc87d3023510 to your computer and use it in GitHub Desktop.
Apex Controller - Approval Request Email
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public without sharing class SBAA_ApprovalRequestEmailController { | |
public Id quoteId { get;set; } | |
public Id approvalId { get; set; } | |
public User recipientUser { get; set; } | |
public Boolean displayRecordChanges { get; set; } | |
public SBQQ__Quote__c quote {get;set;} | |
public sbaa__Approval__c currentApproval {get;set;} | |
public String orgUrl { | |
get; | |
private set; | |
} | |
/** | |
* Retrieves the a collection of record changes for an approval and quote | |
*/ | |
public List<RecordChange> getRecordChanges() | |
{ | |
List<RecordChange> changes = new List<RecordChange>(); | |
if (quoteId == null || approvalId == null) return changes; | |
this.quote = getQuote(); | |
this.currentApproval = getApproval(); | |
this.orgUrl = Url.getOrgDomainUrl().toExternalForm(); | |
List<sbaa__TrackedValue__c> trackedValues = getTrackedValues(); | |
String timeZone = getUserTimeZone(); | |
Map<String, Map<String, FieldMetadata>> objectsWithFieldLabels = getLabelsForTrackedFields(trackedValues); | |
Map<String, Map<String, List<sbaa__TrackedValue__c>>> groupedValues = getGroupedTrackedValues(trackedValues); | |
for (String groupKey : groupedValues.keySet()) { | |
Map<String, List<sbaa__TrackedValue__c>> valuesByField = groupedValues.get(groupKey); | |
if (valuesByField == null || valuesByField.isEmpty()) continue; | |
for (String fieldKey : valuesByField.keySet()) { | |
List<sbaa__TrackedValue__c> values = valuesByField.get(fieldKey); | |
if (values == null | values.isEmpty()) continue; | |
// Store the most recent tracked value to compare with prior changes. | |
sbaa__TrackedValue__c mostRecent = values[0]; | |
Map<String, FieldMetadata> fieldsWithLabels = objectsWithFieldLabels.get(mostRecent.sbaa__TrackedField__r.sbaa__TrackedObject__c); | |
FieldMetaData fm; | |
if (fieldsWithLabels != null) { | |
fm = fieldsWithLabels.get(mostRecent.sbaa__TrackedField__r.sbaa__TrackedField__c); | |
} | |
// Only 1 item means it is the only time a tracked value has occurred. | |
if (values.size() == 1) { | |
changes.add(new RecordChange(fm, mostRecent, null, timeZone)); | |
continue; | |
} | |
// Loop thru the remaining tracked values starting with index 1 (most recent is index 0) | |
// When a prior value is different, there is something to compare current vs previous value. | |
for (Integer i = 1; i < values.size(); i++) { | |
sbaa__TrackedValue__c prior = values[i]; | |
if (prior.sbaa__TrackedField__r.sbaa__TrackedObject__c != mostRecent.sbaa__TrackedField__r.sbaa__TrackedObject__c | |
|| prior.sbaa__TrackedField__r.sbaa__TrackedField__c != mostRecent.sbaa__TrackedField__r.sbaa__TrackedField__c) { | |
continue; | |
} | |
String mostRecentValue = getChangedValue(mostRecent); | |
String priorValue = getChangedValue(prior); | |
if (mostRecentValue != priorValue) { | |
changes.add(new RecordChange(fm, mostRecent, prior, timeZone)); | |
break; | |
} | |
} | |
} | |
} | |
displayRecordChanges = !changes.isEmpty(); | |
return changes; | |
} | |
private SBQQ__Quote__c getQuote() { | |
return [SELECT ID, | |
SBQQ__Opportunity2__c, | |
SBQQ__Opportunity2__r.Name, | |
SBQQ__Opportunity2__r.StageName, | |
SBQQ__Opportunity2__r.Type, | |
SBQQ__Opportunity2__r.CloseDate, | |
SBQQ__Opportunity2__r.Amount, | |
SBQQ__Opportunity2__r.Owner.Name, | |
SBQQ__Opportunity2__r.Account.Name, | |
SBQQ__Opportunity2__r.Account.Owner.Name, | |
Name, | |
SBQQ__SubscriptionTerm__c, | |
SBQQ__StartDate__c, | |
SBQQ__EndDate__c, | |
SBQQ__ListAmount__c, | |
SBQQ__NetAmount__c, | |
SBQQ__TotalCustomerDiscountAmount__c, | |
SBQQ__PaymentTerms__c, | |
SBQQ__BillingFrequency__c | |
FROM SBQQ__Quote__c WHERE ID = :quoteId]; | |
} | |
private sbaa__Approval__c getApproval() { | |
return [SELECT Id, sbaa__Rule__r.Name FROM sbaa__Approval__c WHERE Id =: approvalId]; | |
} | |
/** | |
* Retrieves the a collection of Tracked Values for an approval rule and quote | |
* | |
* Collection is sorted by datetime - most recent changes values first | |
*/ | |
private List<sbaa__TrackedValue__c> getTrackedValues() { | |
sbaa__Approval__c approval = [SELECT Id, sbaa__Rule__c FROM sbaa__Approval__c WHERE ID = :approvalId]; | |
if (approval == null) return new List<sbaa__TrackedValue__c>(); | |
List<sbaa__TrackedValue__c> trackedValues = [ | |
SELECT Id, | |
//CurrencyIsoCode, | |
Quote_Line__c, | |
Quote__c, | |
sbaa__Opportunity__c, | |
Quote_Line__r.SBQQ__ProductName__c, | |
sbaa__TrackedField__r.sbaa__TrackedObject__c, | |
sbaa__TrackedField__r.sbaa__TrackedField__c, | |
sbaa__ValueDate__c, | |
sbaa__ValueNumber__c, | |
sbaa__ValueText__c, | |
SystemModstamp, | |
CreatedDate | |
FROM sbaa__TrackedValue__c | |
WHERE sbaa__TrackedField__r.sbaa__ApprovalRule__c = :approval.sbaa__Rule__c | |
AND ( | |
Quote__c = :quoteId | |
OR Quote_Line__r.SBQQ__Quote__c = :quoteId | |
) | |
ORDER BY CreatedDate DESC | |
]; | |
return trackedValues; | |
} | |
/** | |
* Groups tracked values by Quote, Quote Line or Opportunity ID | |
* @param trackedValues a collection of SBQQ TrackedValue records. | |
*/ | |
private Map<String, Map<String, List<sbaa__TrackedValue__c>>> getGroupedTrackedValues(List<sbaa__TrackedValue__c> trackedValues) { | |
Map<String, Map<String, List<sbaa__TrackedValue__c>>> groupedByRelatedRecords = new Map<String, Map<String, List<sbaa__TrackedValue__c>>>(); | |
// Group Tracked Values by Opportunity, Quote or Quote Line Record ID | |
for (sbaa__TrackedValue__c tv : trackedValues) { | |
String mapKey; | |
if (tv.sbaa__TrackedField__r.sbaa__TrackedObject__c == 'SBQQ__Quote__c') { | |
mapKey = tv.Quote__c; | |
} else if (tv.sbaa__TrackedField__r.sbaa__TrackedObject__c == 'SBQQ__QuoteLine__c') { | |
mapKey = tv.Quote_Line__c; | |
} else { | |
mapKey = tv.sbaa__Opportunity__c; | |
} | |
Map<String, List<sbaa__TrackedValue__c>> valuesByField = groupedByRelatedRecords.get(mapKey); | |
if (valuesByField == null) { | |
valuesByField = new Map<String, List<sbaa__TrackedValue__c>>(); | |
} | |
List<sbaa__TrackedValue__c> values = valuesByField.get(tv.sbaa__TrackedField__r.sbaa__TrackedField__c); | |
if (values == null) { | |
values = new List<sbaa__TrackedValue__c>(); | |
} | |
values.add(tv); | |
valuesByField.put(tv.sbaa__TrackedField__r.sbaa__TrackedField__c, values); | |
groupedByRelatedRecords.put(mapKey, valuesByField); | |
} | |
return groupedByRelatedRecords; | |
} | |
/** | |
* Retrieves the email recipient's time zone. If the recipient is not set, returns the time zone of the current user. | |
*/ | |
private String getUserTimeZone() { | |
if (this.recipientUser == null) { | |
return UserInfo.getTimeZone().getID(); | |
} | |
User u = [SELECT TimeZoneSidKey FROM User WHERE Id = :this.recipientUser.Id]; | |
return u.TimeZoneSidKey; | |
} | |
/** | |
* Retrieves the field label dynamically for the Tracked Fields related to Tracked Values. | |
* | |
* On a tracked field, there are 2 fields the define what is being tracked on a record: | |
* 1. Tracked Object - which object contains the field to track | |
* 2. Tracked Field - the field whose values will be tracked | |
* @param trackedValues a collection of SBQQ TrackedValue records. | |
*/ | |
public Map<String, Map<String, FieldMetadata>> getLabelsForTrackedFields(List<sbaa__TrackedValue__c> trackedValues) { | |
Map<String, List<String>> objectsWithFields = new Map<String, List<String>>(); | |
// Iterate to retrieve SObject fields from the tracked values | |
for (sbaa__TrackedValue__c trackVal : trackedValues) { | |
String key = trackVal.sbaa__TrackedField__r.sbaa__TrackedObject__c; | |
List<String> fields = objectsWithFields.get(key); | |
if (fields == null) { | |
fields = new List<String>(); | |
} | |
fields.add(trackVal.sbaa__TrackedField__r.sbaa__TrackedField__c); | |
objectsWithFields.put(key, fields); | |
} | |
Map<String, Map<String, FieldMetadata>> objectsWithFieldLabels = new Map<String, Map<String, FieldMetadata>>(); | |
if (objectsWithFields.isEmpty()) { | |
return objectsWithFieldLabels; | |
} | |
Schema.DescribeSObjectResult[] descResult = Schema.describeSObjects(new List<String>(objectsWithFields.keySet())); | |
for (Schema.DescribeSObjectResult res : descResult) { | |
List<String> fieldsToGetLabels = objectsWithFields.get(res.getName()); | |
Map<String, SObjectField> fields = res.fields.getMap(); | |
Map<String, FieldMetadata> fieldWithMetadata = new Map<String, FieldMetadata>(); | |
for (String fieldApiName : fieldsToGetLabels) { | |
SObjectField field = fields.get(fieldApiName.toLowerCase()); | |
DescribeFieldResult fieldDesc = field.getDescribe(); | |
FieldMetadata fieldMetadata = new FieldMetadata(); | |
fieldMetadata.ObjectApiName = res.getName(); | |
fieldMetadata.ObjectLabel = res.getLabel(); | |
fieldMetadata.FieldApiName = fieldDesc.getName(); | |
fieldMetadata.FieldLabel = fieldDesc.getLabel(); | |
fieldMetadata.FieldType = fieldDesc.getType(); | |
fieldWithMetadata.put(fieldDesc.getName(), fieldMetadata); | |
} | |
objectsWithFieldLabels.put(res.getName(), fieldWithMetadata); | |
} | |
return objectsWithFieldLabels; | |
} | |
/** | |
* Retrieves the changed value from the Tracked Value record. There are 3 potential fields that store a changed value. This method interprets which field value to return. | |
* | |
* @param trackedValue a SBQQ TrackedValue record. | |
*/ | |
private String getChangedValue(sbaa__TrackedValue__c trackedValue) { | |
if (trackedValue.sbaa__ValueText__c != null) { | |
return trackedValue.sbaa__ValueText__c; | |
} | |
if (trackedValue.sbaa__ValueNumber__c != null) { | |
return String.valueOf(trackedValue.sbaa__ValueNumber__c); | |
} | |
if (trackedValue.sbaa__ValueDate__c != null) { | |
return String.valueOf(trackedValue.sbaa__ValueDate__c); | |
} | |
return null; | |
} | |
public class FieldMetadata { | |
public String ObjectApiName { get; set; } | |
public String ObjectLabel { get; set; } | |
public String FieldApiName { get; set; } | |
public String FieldLabel { get; set; } | |
public Schema.DisplayType FieldType { get; set; } | |
} | |
public enum RecordChangeValueType | |
{ | |
TextValue, | |
DateValue, | |
CurrencyValue, | |
NumberValue, | |
PercentValue | |
} | |
public class RecordChange { | |
public String ObjectName { get; set;} | |
public String FieldName { get; set;} | |
public String ValueChangedOn { get; set;} | |
public String CurrencyIsoCode { get; set; } | |
public String OriginalValue { get; set;} | |
public Date OriginalValueDate { get; set; } | |
public Decimal OriginalValueDecimal { get; set; } | |
public String NewValue { get; set;} | |
public Date NewValueDate { get; set; } | |
public Decimal NewValueDecimal { get; set; } | |
public RecordChangeValueType ValueType { get; set; } | |
public Boolean IsTextValue { | |
get { | |
return ValueType == RecordChangeValueType.TextValue; | |
} | |
} | |
public Boolean IsDateValue { | |
get { | |
return ValueType == RecordChangeValueType.DateValue; | |
} | |
} | |
public Boolean IsNumericValue { | |
get { | |
return ValueType == RecordChangeValueType.NumberValue; | |
} | |
} | |
public Boolean IsCurrencyValue { | |
get { | |
return ValueType == RecordChangeValueType.CurrencyValue; | |
} | |
} | |
public Boolean IsPercentValue { | |
get { | |
return ValueType == RecordChangeValueType.PercentValue; | |
} | |
} | |
public String ProductName { get; set; } | |
public String ChangeReason { | |
get { | |
String fmt = '{0} - {1}'; | |
List<Object> fmtOptions = new List<Object>(); | |
if (ProductName == null) { | |
fmtOptions.add(ObjectName); | |
} else { | |
fmtOptions.add(ProductName); | |
} | |
fmtOptions.add(FieldName); | |
fmt += (OriginalValue == null && OriginalValueDate == null && OriginalValueDecimal == null) ? ' initial value was set' : ' was changed'; | |
return String.format(fmt, fmtOptions); | |
} | |
} | |
protected final String dateTimeFormatter = 'MM/dd/yyyy hh:mmaaa'; | |
public RecordChange() {} | |
public RecordChange(FieldMetadata fm, sbaa__TrackedValue__c currentValue, sbaa__TrackedValue__c priorValue, String timeZone) { | |
this.ValueChangedOn = currentValue.SystemModstamp.format(dateTimeFormatter, timeZone); | |
this.ProductName = currentValue.Quote_Line__r.SBQQ__ProductName__c; | |
this.CurrencyIsoCode = 'USD'; | |
// Uncomment the following line if multi-currency is enabled. | |
//this.CurrencyIsoCode = currentValue.CurrencyIsoCode; | |
this.ObjectName = (fm != null && fm.ObjectLabel != null) ? fm.ObjectLabel : ''; | |
this.FieldName = (fm != null && fm.FieldLabel != null) ? fm.FieldLabel : ''; | |
setValueFields(currentValue, priorValue, fm); | |
} | |
private void setValueFields(sbaa__TrackedValue__c currentValue, sbaa__TrackedValue__c priorValue, FieldMetadata fm) { | |
if (currentValue.sbaa__ValueText__c != null) { | |
this.ValueType = RecordChangeValueType.TextValue; | |
this.NewValue = currentValue.sbaa__ValueText__c; | |
this.OriginalValue = (priorValue != null) ? priorValue.sbaa__ValueText__c : null; | |
return; | |
} | |
if (currentValue.sbaa__ValueNumber__c != null) { | |
switch on fm.FieldType { | |
when CURRENCY { | |
this.ValueType = RecordChangeValueType.CurrencyValue; | |
} | |
when PERCENT { | |
this.ValueType = RecordChangeValueType.PercentValue; | |
} | |
when else { | |
this.ValueType = RecordChangeValueType.NumberValue; | |
} | |
} | |
this.NewValueDecimal = currentValue.sbaa__ValueNumber__c; | |
this.OriginalValueDecimal = (priorValue != null) ? priorValue.sbaa__ValueNumber__c : null; | |
return; | |
} | |
if (currentValue.sbaa__ValueDate__c != null) { | |
this.ValueType = RecordChangeValueType.DateValue; | |
this.NewValueDate = currentValue.sbaa__ValueDate__c; | |
this.OriginalValueDate = (priorValue != null) ? priorValue.sbaa__ValueDate__c : null; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment