4 min read

Multi-currency Support for Rollups

I recently came across a requirement to support multi-currency for a rollup trigger where the parent object and child object each maintained their own currencies. The objects where not part of a master-detail relationship (per the requirements) and it was a valid use-case that each object should maintain currency independently.

The client had a previous implementation of rollups that were handled using a trigger but quickly realized that there was an issue with the numbers when the currencies between the two objects did not match.

In troubleshooting the issue, it was discovered that the child object's currency value was being saved as if the value was in the parent object's currency. Another issue is that since each child object could maintain their own currency it meant that a given parent record could have 1 or more child records, each with a different currency code.

The best way I found to solve for this was to develop a method for converting one currency value to another. It would be nice if Salesforce provided this type of method natively but one does not seem to exist based on my research.

Note that this tutorial only pertains to orgs that have multi-currency enabled. This is a feature that needs to be enabled by Salesforce.com for your particular instance and can easily be done by logging a ticket.


More details on enabling multi-currency can be found on the Salesforce Help Portal.

Let's start this tutorial with the the utility class that will contain our conversion method. I have some very specific requirements that I want to make sure this class solves for:

  1. Avoid the need to query the currency rate table every time a conversion call is made
  2. Avoid the need to loop through the rates every time one is needed by the method
  3. The method should be easily called without the need to instantiate an object

These three requirements are easily addressed by using a singleton pattern that allows a single instance of the object to be accessed by multiple processes within a given transaction. When first instantiated, the object stores the currency rates in a map by IsoCode as an instance variable. Finally, the conversion method itself is a static method so that it can easily be called without the need to instantiate and keep track of an object reference.

Now let's take a look at the details of the currency conversion method itself.

public static decimal convertCurrency(string fromIsoCode, string toIsoCode, decimal value) {
    MultiCurrencyUtil util = MultiCurrencyUtil.getInstance();
    decimal convertedValue = value;

    boolean conversionRequired = (fromIsoCode != toIsoCode && value != null);
    boolean fromIsoCodeValid = util.IsoCurrencyTypes.containsKey(fromIsoCode);
    boolean toIsoCodeValid = util.IsoCurrencyTypes.containsKey(toIsoCode);
    
    if (conversionRequired && fromIsoCodeValid && toIsoCodeValid) {
        CurrencyType fromCurrencyType = util.IsoCurrencyTypes.get(fromIsoCode);
        convertedValue = value / fromCurrencyType.ConversionRate;

        CurrencyType toCurrencyType = util.IsoCurrencyTypes.get(toIsoCode);
        convertedValue = convertedValue * toCurrencyType.ConversionRate;
    }

    return convertedValue;
}

The sample method is very straight forward but its worth calling attention to some key components of the method.

The first thing you should notice is that it grabs an instance of the utility class. This is what allows the method to gain access to currency rates that are stored in memory. Otherwise, every time you call this method it would need to query the currency rates resulting in query limit exceptions.

The next few lines of code test whether a conversion is required and if the to and from CurrencyIsoCodes are valid. Once we know that a conversion is necessary and possible the code divides the supplied value by the from currency rate to essentially convert it down to the corporate currency. Then it muliplies the quotient by the to currency rate and returns the resulting product.

Because the conversion method always converts down to the corporate currency the code can concentrate on multiplying by the to currency rate greatly simplifying the code. In this case, the code doesn't even need to know anything about the corporate currency because all rates are relative the corporate currency.

So there you have it, a ready made solution for rolling up currency rates where each child object is maintaining their own currency. However, this solution doesn't take into account Advanced Currency Management, which requires a bit of modification to the currency conversion and query methods so that the start date of the currency rate can be specified.

Now that we have all of the pieces in place we can put this class to use. For this example I will do a rollup for a fictional set of objects called "Parent" and "Child". The parent object contains a field called Total Amount and the child object contains a field called Unit Cost. Also, my currency rate table has an entry for USD with a rate of 1.00 and GBP with a rate of 0.62.

Here is the helper class that will perform a rollup of child unit costs to the total amount on the parent object.

public without sharing class ParentChildRollupHelper {

    public void rollupChildrenToParent(Set<Id> parentIds) {
        List<Parent__c> parents = queryParents(parentIds);

        if (parents.isEmpty() == false) {
            List<Parent__c> parentsToUpdate = new List<Parent__c>();

            for (Parent__c parent : parents) {
                string toIsoCode = parent.CurrencyIsoCode;

                parent.Total_Amount__c = 0;

                for (Child__c child : parent.Children__r) {
                    string fromIsoCode = child.CurrencyIsoCode;
                    parent.Total_Amount__c += MultiCurrencyUtil.convertCurrency(fromIsoCode, toIsoCode, child.Unit_Cost__c);
                }
                
                parentsToUpdate.add(parent);
            }

            if (parentsToUpdate.isEmpty() == false) {
                update parentsToUpdate;
            }
        }
    }

    //Query Methods
    private List<Parent__c> queryParents(Set<Id> parentIds) {
        return [SELECT
                    Id
                    ,Name
                    ,Total_Amount__c
                    ,CurrencyIsoCode
                    ,(SELECT
                            Id
                            ,Unit_Cost__c
                            ,CurrencyIsoCode
                        FROM
                            Children__r)
                FROM
                    Parent__c
                WHERE
                    Id IN :parentIds];
    }
}

It is a pretty simple implementation, you just pass in the record ids for the parent object and the rollup method will query the related child records and calculated the Total Amount. As demonstrated, the method makes use of the MultiCurrencyUtil class to convert the child records Unit Cost value to match the currency of the parent object.

Here is the complete MultiCurrencyUtil class:

public with sharing class MultiCurrencyUtil {
    //Protected Members
    private static MultiCurrencyUtil instance;

    //Properties
    public final Map<string, CurrencyType> IsoCurrencyTypes;

    //Constructors
    private MultiCurrencyUtil() {
        IsoCurrencyTypes = getCurrencyTypesByIsoCode();
    }

    //Static Methods
    public static MultiCurrencyUtil getInstance() {
        if (instance == null) {
            instance = new MultiCurrencyUtil();
        }

        return instance;
    }

    public static decimal convertCurrency(string fromIsoCode, string toIsoCode, decimal value) {
        MultiCurrencyUtil util = MultiCurrencyUtil.getInstance();
        decimal convertedValue = value;

        boolean conversionRequired = (fromIsoCode != toIsoCode && value != null);
        boolean fromIsoCodeValid = util.IsoCurrencyTypes.containsKey(fromIsoCode);
        boolean toIsoCodeValid = util.IsoCurrencyTypes.containsKey(toIsoCode);
        
        if (conversionRequired && fromIsoCodeValid && toIsoCodeValid) {
            CurrencyType fromCurrencyType = util.IsoCurrencyTypes.get(fromIsoCode);
            convertedValue = value / fromCurrencyType.ConversionRate;

            CurrencyType toCurrencyType = util.IsoCurrencyTypes.get(toIsoCode);
            convertedValue = convertedValue * toCurrencyType.ConversionRate;
        }

        return convertedValue;
    }
    
    //Private Methods
    private Map<string, CurrencyType> getCurrencyTypesByIsoCode() {
        Map<string, CurrencyType> currencyTypesByIsoCode = new Map<string, CurrencyType>();

        List<CurrencyType> currencyTypes = queryCurrencyTypes();
        for (CurrencyType currencyType : currencyTypes) {
            currencyTypesByIsoCode.put(currencyType.IsoCode, currencyType);
        }

        return currencyTypesByIsoCode;
    }

    //Query Methods
    private List<CurrencyType> queryCurrencyTypes() {
        return [SELECT
                    Id
                    ,IsoCode
                    ,ConversionRate
                    ,IsCorporate
                FROM
                    CurrencyType];
    }
}