Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ryanfollmer/431e89b96d46444b0f2ecc87d3023510 to your computer and use it in GitHub Desktop.
Save ryanfollmer/431e89b96d46444b0f2ecc87d3023510 to your computer and use it in GitHub Desktop.
Apex Controller - Approval Request Email
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