Troubleshooting Scripts: Rollups

Resource Hero rolls up totals from Resource Assignment records to the parent record they are related to, such as an Opportunity, a Project, or a custom object. If a total on a parent record looks wrong, use the following script to find every parent record where the rollup field does not match the sum of its Resource Assignment records.

The script only reads data. It does not change any records.

Why a script is needed

A single SOQL query cannot compare a field on a parent record to a total calculated from its child records. The script runs two queries, one for the totals from the Resource Assignment object and one for the parent records, and compares the results.

Before you run the script

Set the three values at the top of the script:

  • parentObject – The API name of the parent object to check, for example Project__c or Opportunity.
  • parentField – The API name of the rollup field on the parent object. For forecast hours this is RH_Total_Hours__c.
  • assignmentField – The API name of the source field on the Resource Assignment object. For forecast hours this is ResourceHeroApp__Hours__c.

To confirm which fields are paired, from Setup, enter Custom Settings in Quick Find, then select Custom Settings. Click Manage next to RHA Rollup Fields. Each entry lists a Parent Field Name and a Resource Assignment Field Name.

Note:

If you are checking the RH Project object that is included with Resource Hero, add the namespace prefix to the parent values: ResourceHeroApp__RH_Project__c and ResourceHeroApp__RH_Total_Hours__c.

Find records where the rollup does not match

// The parent object and the pair of rollup fields to check
String parentObject = 'Project__c';
String parentField = 'RH_Total_Hours__c';
String assignmentField = 'ResourceHeroApp__Hours__c';

// Read the Id prefix and the decimal places of the rollup field from the parent object
Schema.DescribeSObjectResult parentDescribe = Schema.getGlobalDescribe().get(parentObject).getDescribe();
String prefix = parentDescribe.getKeyPrefix() + '%';
Integer scale = parentDescribe.fields.getMap().get(parentField).getDescribe().getScale();

// Total the source field from all resource assignments, grouped by the related parent record
Map<Id, Decimal> expected = new Map<Id, Decimal>();
List<AggregateResult> totals = Database.query(
    'SELECT ResourceHeroApp__relatedId__c rid, SUM(' + assignmentField + ') total' +
    ' FROM ResourceHeroApp__Resource_Assignment__c' +
    ' WHERE ResourceHeroApp__relatedId__c LIKE :prefix' +
    ' GROUP BY ResourceHeroApp__relatedId__c'
);
for (AggregateResult ar : totals) {
    Decimal total = (Decimal) ar.get('total');
    expected.put(Id.valueOf(String.valueOf(ar.get('rid'))), total == null ? 0 : total);
}

// Compare each parent record to its total. A parent with no assignments is expected to be 0
Integer mismatched = 0;
for (SObject parent : Database.query('SELECT Id, ' + parentField + ' FROM ' + parentObject)) {
    Decimal actual = (Decimal) parent.get(parentField);
    actual = actual == null ? 0 : actual;
    Decimal want = expected.containsKey(parent.Id) ? expected.get(parent.Id) : 0;
    want = want.setScale(scale, RoundingMode.HALF_UP);
    if (actual != want) {
        mismatched++;
        System.debug('MISMATCH ' + parent.Id + ' - rollup: ' + actual + ' - assignments: ' + want);
    }
}

// Print a count of the records that do not match
System.debug('Mismatched records: ' + mismatched);

Run the script

  1. Click the gear icon, then select Developer Console.
  2. Select Debug → Open Execute Anonymous Window.
  3. Paste the script and update the three values at the top.
  4. Select the Open Log checkbox, then click Execute.
  5. In the log, select the Debug Only checkbox.

Each record that does not match is listed on a line that starts with “MISMATCH”, followed by the record Id, the value in the rollup field, and the total from its Resource Assignment records. The last line shows the count of records that do not match.

What the results mean

  • No records listed – The rollup field is accurate for every record on the object.
  • A record was updated in the last hour – Rollups are processed asynchronously. A record with a rollup that is still waiting in the queue is listed until the rollup is processed. Run the script again later.
  • A record is listed every time – The rollup is failing or was never processed. Check the RHA Async Queue object for an error on that record, then reprocess the rollups.
Note:

In an org with multiple currencies, use this script for hours fields. Amount and cost rollups are converted to the currency of the parent record, so the two values can differ even when the rollup is correct.

Fix records that do not match

To correct the records, follow the steps in Reprocess all rollups. If the same records are listed after reprocessing, see Rollup Retry Handling and Error Logging to find the error that is blocking the rollup.


Related Support Posts:

Ready to get started?

Schedule a call to see if Resource Hero is right for you

Book now