3

I have a function in Apex that sets a few fields on my custom object and that also calls the recalculateFormulas() function on the object. I call my function from Salesforce through a custom button.

Setting the fields all goes according to plan, but once I call the recalculateFormulas() function I get an unexpectedException.

System.UnexpectedException: Unable to create/update fields: 
LastModifiedDate, CreatedById, IsDeleted, LastViewedDate, CreatedDate, 
LastReferencedDate, SystemModstamp, LastModifiedById. Please check the 
security settings of this field and verify that it is read/write for 
your profile permission set.

My issue is very similar to this one. Only my exception complains about a few more fields. Sadly no real solution was posted on that question apart from that it is a possible bug (back in 2015).

Now I am pretty sure all these fields are standard read only fields from Salesforce so I understand that I can't change them (I don't even want to). But I do wonder why the recalculateFormulas() function tries to change them regardless. Am I using the function in the wrong context? Is it deprecated?

My function looks like this:

webService static void generateRequest(Id deliveryId) {
    Delivery__c delivery = DeliveryDocumentGenerator.getDelivery(deliveryId);
    Product2 product = DeliveryDocumentGenerator.getInternetProduct(delivery.Opportunity__c);
    if (null != product) {
        delivery.Internet_product__c = product.Id;
        delivery.Create_Delivery__c = true;
        delivery.Error_message__c = '';
    } else {
        delivery.Error_message__c = 'Could not find internet product';
    }

    update delivery;

    delivery.recalculateFormulas();
}

I've tried moving the recalculateFormulas function above my update but that didn't change anything.

  • 1
    I don't think this bug has been fixed on the platform yet. The only workaround suggested by Salesforce is Avoid retrieving lookup fields in SOQL queries that retrieves the target sObjects – Jarvis Aug 14 '18 at 10:20
  • @Bennie Thanks! It's not too hard for me to work around it. So I'll just do that. Is there a page where these bugs are tracked or where we can see statuses on them? – Dirk Scholten Aug 15 '18 at 7:51
  • 1
    Yes, there is an "Known Issues" page on the community - success.salesforce.com/issues_index. And for this specific issue this is the page - success.salesforce.com/issues_view?id=a1p300000008bvw – Jarvis Aug 15 '18 at 9:43
2

You can get around these errors by creating a clone of the object, without the system fields in memory, and calling the function on the copy. See this example:

// Starting Record 
Account a = [SELECT Id, Name, CreatedDate, IsDeleted FROM Account LIMIT 1];

// Copy of starting record - without system fields 
Account b = new Account (
    Id = a.Id, 
    Name = a.Name
);

// A recalc fails 
// a.recalculateFormulas(); 

// B recalcs without errors 
b.recalculateFormulas(); 

Might not be the best solution - but if the clone is local to a helper function, you keep the page state smaller, the code doesn't look confusing, and you can copy the updated formula values back into your starting object.


Heres a generic function to re-calculate a records formula fields. This is probably overkill, and ends up calling JSON like 4 times, plus describes, plus keeping two records in memory, plus the field maps, its just really expensive. It works however, you can pass in any object, and wind up with refreshed values in the returned objects formulas.

// Starting Record 
// Clean name is a formula which removes special characters from the name in order to run comparisons 
Account a = [SELECT Id, Name, Clean_Name__c , CreatedDate, IsDeleted FROM Account LIMIT 1];

a.Name = 'UNclean--??Name&&';

// Copy of starting record - without system fields 
Account b = new Account (
    Id = a.Id, 
    Name = a.Name
);

// A recalc fails 
// a.recalculateFormulas(); 

// B recalcs without errors 
b.recalculateFormulas(); 

System.debug(b.Clean_Name__c); // "UNcleanName"

a = (Account)SafeRecalc(a);

System.debug(a.Clean_Name__c); // "UNcleanName"
System.debug(a.CreatedDate); // Valid Date - mostly here to make sure system fields were carried over 
System.assertEquals(a.Clean_Name__c, b.Clean_Name__c); // true! 


// Expensive, but typesafe 
public static sObject SafeRecalc(sObject record) {
    // New Instance with id 
    sObject clone = record.getSObjectType().newSObject(record.Id); 

    // Keep describe handy 
    Map<String, Schema.SObjectField> fields = record.getSObjectType().getDescribe().fields.getMap();  

    // Get fields with values 
    Map<String, Object> populatedFields = record.getPopulatedFieldsAsMap();   

    // Keep system fields in own list - merge into cloned record after recalc 
    Map<String, Object> nonEditableFields = new Map<String, Object>(); 

    // Set field values for non-standard fields 
    for (String key:populatedFields.keySet()) {
        // Any values we can safely pass along are "cloned" into the new record 
        if (fields.get(key).getDescribe().isUpdateable()) {
            clone.put(key, populatedFields.get(key));
        } else if (!fields.get(key).getDescribe().isCalculated()) {
            // Any system fields, non-editable fields, go here 
            nonEditableFields.put(key, populatedFields.get(key)); 
        }
    }

    clone.reCalculateFormulas(); // safe recalc 

    // Need to update read-only fields
    // So we use a json map to do that
    // We create a map based on the fields, then merge the non-editable fields into the map 
    Map<String, Object> untypedClone = (Map<String, Object>)JSON.deserializeUntyped(JSON.serialize(clone));

    for (String key: nonEditableFields.keySet()) {
        untypedClone.put(key, nonEditableFields.get(key));
    }

    // Then we re-seralize the value, then de-seralize it as a instance of the given type, then cast it back to an sObject to match our record 
    record = (sObject)JSON.deserialize(JSON.serialize(untypedClone), Type.forName(record.getSObjectType().getDescribe().getName())); 

    return record; 
}
  • When I try to copy your code in the 1st grey box into a new apex class why do I get the error "Invalid constructor name: b.recalculateFormulas"? – compski Sep 25 '18 at 16:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.