function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
DBManagerDBManager 

ENTITY IS LOCKED Error when updating child record when parent is approved

We have built a Purchase Order system into Salesforce. The Purchase Order object is a parent of the Purchase Item object, and when the PO is approved, a trigger fires which updates all the PIs with the value of the PO Stage field.

 

However, that trigger prevents the Purchase Order from being approved, as it generates the following error:

There were custom validation error(s) encountered while saving the affected record(s). The first validation error encountered was "Apex trigger PurchaseOrderTrigger caused an unexpected exception, contact your administrator: PurchaseOrderTrigger: execution of AfterUpdate caused by: System.DmlException: Update failed. First exception on row 0 with id a0W200000091BnTEAU; first error: ENTITY_IS_LOCKED, the entity is locked for editing: []: Class.PurchaseItemTriggerMethods.copyStageToStatusFromOrder: line 131, column 1".

 

Here is the firing trigger:

trigger PurchaseOrderTrigger on Purchase_Orders__c (after update) 
{
    PurchaseItemTriggerMethods.copyStageToStatusFromOrder(Trigger.newMap);
}

 

And here are the relevant lines of the class:

    public static void copyStageToStatusFromOrder(Map<Id, Purchase_Orders__c> purchOrderMap)
    {
    
        Map<Id,Purchase_Item__c> purchItemMap = new Map<Id,Purchase_Item__c>([Select Status_Copy__c, Stage__c From Purchase_Item__c Where Purchase_Order__c IN :purchOrderMap.KeySet()]);
        Map<Id,Purchase_Item__c> purchItemMapEmpty = new Map<Id,Purchase_Item__c>(); 
        copyStageToStatusFromItem(purchItemMap.Values(), purchItemMapempty, True);
        Update purchItemMap.Values();
   
    }

 

From my research I have found that it might be something to do with using an after update and the Trigger.newMap value. However, I have not found the right changes that will fix the issue.

 

Can anyone help?

Best Answer chosen by Admin (Salesforce Developers) 
DBManagerDBManager

I managed to solve this by changing the approval process to allow the current approver to edit the record during the approval process.

 

So the child records can be updated by the parent when they approve.

All Answers

Starz26Starz26

Something on the child is trying to update the parent when you update the child (roll-up summary, trigger, etc)

 

Try simply changing the trigger to a before update..

DBManagerDBManager

Hi Starz26

 

I tried that but there is a test class system.assert which checks the status update has happened, and it now fails.

 

Any other ideas?

 

Thanks.

Starz26Starz26

Then you will have to debug your code to find out why the status is changed.

 

When you are doing the system assert, did you query for the record again before doing the assert?

 

You cannot just use the inserted record to do the assert. For example this WILL NOT work:

 

Account a = New Account(Name = 'Test');

Insert a;

 

//Trigger updated a Custome__c field

 

system.assertEquals(True,a.Custom__c != Null);

 

This will fail as the a variable is not updated with new values except for the ID.

 

You have to query before the systemassert:

 

a = [Select Custom__c From Account where ID = :a.id];

system.assertEquals(True,a.Custom__c != Null);

 

This will work.

 

So either your trigger is not doing what it is supposed to or your test method is not testing properly. The simple act of doing a before vs after insert should not make your test methodsfail as everything is done after the record is committed regardless of the trigger context.

DBManagerDBManager

Thanks for your reply again, Starz26.

 

I have tried debugging the code, but I can't find any clues.

 

Here is the test class which, as you described, re-queries before the system.assert (the last few lines are the most relevant):

    // Test methods
    public static testMethod void testPurchaseItemWithinBudget()
    {
        Map<String, Id> recordTypes = CampdenCommon.GetRecordTypes(new List<String>{ 'Account' });
        Issue_Year__c newEvent = new Issue_Year__c(name='2020 Test Event', Mag_Or_Event__c='Event', Description__c='2020 Test Event');
        insert newEvent;
        Budget_Record__c newBudget = new Budget_Record__c(Name='Campden Test Budget 2020', Budget__c=100000.00, Event_Issue__c=newEvent.Id);
        insert newBudget;
        Account newAccount = new Account(name='Test2020', recordTypeId=recordTypes.get('Supplier'),SUN_Account_Code__c='SunCo');
        insert newAccount; 
        Purchase_Orders__c newPurch = new Purchase_Orders__c(Supplier__c=newAccount.id, Currency__c='GBP', Invoice_To__c = 'Campden Publishing Ltd');
        insert newPurch;
        Purchase_Orders__c newPurch1 = [Select Stage__c From Purchase_Orders__c Where id = :newPurch.Id]; 
        System.debug('Purchase Order Stage:'+newPurch1.Stage__c);
        Purchase_Item__c newPurchItem = new Purchase_Item__c(Purchase_Order__c=newPurch.Id, RecordTypeId = '01220000000AHSf', description__c='Test Item 1',Budget_Record__c=newBudget.Id, Amount__c=120.00, Event_Issue__c=newEvent.Id);
        Insert newPurchItem;
        Purchase_Item__c retPurchItem0 = [Select Status_Copy__c, Purchase_Order__r.Stage__c, Within_Budget__c From Purchase_Item__c Where Id=:newPurchItem.Id];
        System.assert(retPurchItem0.Status_Copy__c==retPurchItem0.Purchase_Order__r.Stage__c);
        newBudget.Budget__c = 10;
        test.startTest();
        update newBudget;
        test.stopTest();
        Purchase_Item__c retPurchItem1 = [Select Within_Budget__c From Purchase_Item__c Where Id=:newPurchItem.Id];
        System.assert(retPurchItem1.Within_Budget__c==False);
        newPurch.Cancelled__c = True;
        newPurch.Reason_for_Cancellation__c='Wrong Order';
        //test.startTest();
        update newPurch;
        //test.stopTest();
        Purchase_Item__c retPurchItem2 = [Select Purchase_Order__r.Stage__c, Status_Copy__c, Status__c From Purchase_Item__c Where Id=:newPurchItem.Id];
        System.debug('ORDER STAGE......'+retPurchItem2.Purchase_Order__r.Stage__c);
        System.debug('ITEM STAGE......'+retPurchItem2.Status_Copy__c);
        System.assert(retPurchItem2.Status_Copy__c==retPurchItem2.Purchase_Order__r.Stage__c);

    } 

 

Any help?

Starz26Starz26

When you do this via the UI (Not test methods) does it work??

 

If so then you are going to have to go through your code line by line, review what the values should be, compare them to what they are, and find out where it is going wrong.

 

If it works in the UI it should work in the test method....

 

Another way to go about it:

 

- Following exactly what your test method is doing, complete those steps in sequence LIVE (via the UI). See what the results are.

 

It is difficult to follow your code so you are going to have to do a bit of the work using debug logs, developer console, etc.

 

The key part is that changing it from after update to before update should not afftect your test methods....either the trigger is not correct or the test methods are not correct.....IMHO

 

 

DBManagerDBManager

Thanks for your feedback, again, Starz26.

 

So it seems, at least partly, that I was being an idiot - the test class was working and correct. It seems the trigger is not working in the 'before update' position.

 

I have a theory that this may be something to do with the fact that the field that is being used to update another field is formula. I think the value of the field is not recalculated until after the update, and therefore the trigger updates with the wrong value.

 

Here are the workings of the trigger:

    public static void copyStageToStatusFromItem(List<Purchase_Item__c> purchItemList, Map<Id,Purchase_Item__c> oldPurchItemMap, Boolean FromOrder)
    {
        for(Purchase_Item__c pi:purchItemList)
        {
            if(FromOrder||trigger.isInsert||(trigger.IsUpdate&&pi.stage__c!=oldPurchItemMap.get(pi.Id).stage__c))
                pi.Status_Copy__c = pi.Stage__c;
        }
    }
    
    public static void copyStageToStatusFromOrder(Map<Id, Purchase_Orders__c> purchOrderMap)
    {
        Map<Id,Purchase_Item__c> purchItemMap = new Map<Id,Purchase_Item__c>([Select Status_Copy__c, Stage__c From Purchase_Item__c Where Purchase_Order__c IN :purchOrderMap.KeySet()]);
        Map<Id,Purchase_Item__c> purchItemMapEmpty = new Map<Id,Purchase_Item__c>(); 
        copyStageToStatusFromItem(purchItemMap.Values(), purchItemMapempty, True);
        Update purchItemMap.Values();
    }

 

  • Purchase_Order.Stage__c is a formula field
  • Purchase_Item.Stage__c is a formula field, that reflects the value of Purchase_Order.Stage__c
  • Purchase_Item.Status_Copy__c is a text field

Let me know if this makes sense, and if I'm right about the problem.

 

Thanks again.

DBManagerDBManager

I managed to solve this by changing the approval process to allow the current approver to edit the record during the approval process.

 

So the child records can be updated by the parent when they approve.

This was selected as the best answer
Ravi Rao ArrabelliRavi Rao Arrabelli
Worked like a charm by providing access to the approver to edit the records.
Thank you.