2

I have a simple Before Update Trigger that I want to run when anything on the Accounting_Instructions__c is updated. I am not sure why my trigger is not working. I either receive a NullPointerException or the field doesn't update.

I have tried running it as an after trigger and doing DML into a new instance of the object. I have tried looping through a list of the objects to be updated, I have tried to have a nested loop over the Case object (the Status field on Case is what I am trying to update). I have tried a simple field update in a single for loop but get NullPointerException. I know that trigger.new is not null, I have system.debugged it and have been returned values.

This is my first trigger so I am sure it is a fundamental oversight but any advice is welcome :)

//Requirements: Pull all Account Instruction records, related to the Case I am on, that have a Case status of "Sent To Accounting"
//Pull all Account Instruction records, related to the Case I am on, that have "Accounting Complete" field checked
//Update Status on current Case to 'Complete'
//run this trigger when Accounting Instructions field "Accounting Complete" is updated

trigger CompletedAccountingInstructions on Accounting_Instructions__c (before update) {
    for(Accounting_Instructions__c aic : Trigger.new) {
        //aic.Case__r.Status = 'Open'; this seems like the easiest path but I receive a Null Pointer Exception when I do it this way
    }
    Accounting_Instructions__c[] instructions = [SELECT Id, Accounting_Complete__c, Case__r.Status FROM Accounting_Instructions__c WHERE Case__r.Status = 'Sent To Accounting' AND Accounting_Complete__c = TRUE AND Id IN :Trigger.new];

    for(Accounting_Instructions__c aic : instructions){
        aic.Case__r.Status = 'Open';
    }
}

The only thing that I found strange was when I system.debugged the trigger.new or the aic variable, I was returned:

    trigger.new:
 (Accounting_Instructions__c:{Id=a1Nc0000002sssssI, OwnerId=0053400sssssQnEtAAK, IsDeleted=false, Name=ACTG-00014, CreatedDate=2017-04-28 19:19:51, CreatedById=00534000009QnsssK, LastModifiedDate=2017-05-01 15:31:58, LastModifiedById=005340000ssssAAK, SystemModstamp=2017-05-01 15:31:58, LastViewedDate=null, LastReferencedDate=null, Case__c=500c000sssssAsAAK, Action__c=Do not rerun;Rerun;Rerun w/ fee, Rerun_ACH_Date__c=2017-04-19 00:00:00, Resume_ACH_Date__c=2017-04-20 00:00:00, Other_Comments__c=Test Comment 2, Amount__c=1.00, Send_to_Accounting__c=false, Accounting_Complete__c=true, Loan_Number__c=null, Legal_Name__c=null, Date_of_Bounce__c=null, Debit_Amount__c=0.00, Returned_Payment_Type__c=null, Duplicate_Comments__c=null, Payment_Type__c=null, **Case_Status__c=Sent to Accounting})**

Which has the field **Case_Status__c** on the object. This is different then **Case__r.Status.** Could this be the issue? I have tried to change the field name but I am rejected as it is not a field on Accounting_Instructions__c.

UPDATES:

With Adrian's update:

public with sharing class AccountingInstructionsService
{
   public static void openParentCases(List<Accounting_Instructions__c> records)
   {
      Map<Id, Case> parentCases = new Map<Id, Case>();
        for (Accounting_Instructions__c record : records)
        {
            if(record.Accounting_Complete__c == TRUE) {
                if(record.Case__r.Status == 'Sent to Accounting') {
                    parentCases.put(record.Case__c, new Case(Id = record.Case__c, Status = 'New'));
                }
            }
        }
        parentCases.remove(null);
        List<Case> caseList = parentCases.values();
        update caseList;
    }
}

UPDATE 2:

I took what Adrian gave me and added a little more to be able to search for all of the Accounting_Instructions__c's on the Case. See below result:

public with sharing class AccountingInstructionsService
{
    public static void openParentCases(List<Accounting_Instructions__c> records)
    {
       Id caseID;
       Integer i = 0;
       list<Accounting_Instructions__c> aic = [SELECT Accounting_Complete__c, Case__c FROM Accounting_Instructions__c WHERE Id IN :records];
       for(Accounting_Instructions__c a : aic){
           caseID = a.Case__c;
       }
       List<Accounting_Instructions__c> listOfAccountInstrRelatedToCase = [SELECT Id, Accounting_Complete__c, Case__c FROM Accounting_Instructions__c WHERE Case__r.Id = :caseID];
       List<Case> parentCases;
       Set<Id> caseIds = new Set<Id>();
       for (Accounting_Instructions__c record : listOfAccountInstrRelatedToCase) {
           if(record.Accounting_Complete__c == TRUE){
               caseIds.add(record.Case__c);
               i = i + 1;
           }
           else{
              i = i - 1;
           }
       }
       if(i == listOfAccountInstrRelatedToCase.size()) {
        parentCases = [SELECT Id, Status FROM Case WHERE Id IN :caseIds AND Status = 'Sent to Accounting'];
           if(!parentCases.isEmpty()) {
               for (Case parentCase : parentCases) {
                   parentCase.Status = 'Completed';
               }
           }
           update parentCases;
       }
   }
}

2 Answers 2

6

You can't set cross object fields on trigger context records.

When you specify Case__r on a record in your trigger.new collection, it is null. Hence trying to set its Status results in an attempt to de-reference a null variable.

for (Accounting_Instructions__c record : trigger.new)
{
    // below is the same as record.Case__r.Status = 'Open'
    Case parentCase = record.Case__r;
    parentCase.Status = 'Open';
    // related record data is not defined on trigger context records
    // hence parentCase is null
}

Even if Case__r were defined, you cannot update related records in that way. In your code, you need to act on a separate Case instance/collection, not Accounting_Instructions__c. Implement a Service Layer along these lines:

public with sharing class AccountingInstructionsService
{
    public static void openParentCases(List<Accounting_Instructions__c> records)
    {
        Map<Id, Case> parentCases = new Map<Id, Case>();
        for (Accounting_Instructions__c record : records)
        {
            parentCases.put(record.Case__c, new Case(Id=record.Case__c, Status='Open');
        }
        parentCases.remove(null);
        update parentCases.values();
        // add error handling
        // omitted here for brevity
    }
}

When updating records, you only need to know the Id. Since in this case the lookup contains that value, you don't need to query for the parent Case records. Unless, that is, you want to filter on those records. In that case, you need to query for them.

public static void openParentCases(...)
{
    Set<Id> caseIds = new Set<Id>();
    for (Accounting_Instructions__c record : records) caseIds.add(record.Case__c);
    
    List<Case> parentCases = [
        SELECT Id FROM Case WHERE Id IN :caseIds
        AND Status = 'Sent to Accounting'
    ];
    for (Case parentCase : parentCases) parentCase.Status = 'Open';
    update parentCases;
}

Either way, from your trigger you would just call:

AccountingInstructionsService.openParentCases(trigger.new)

Note that cross object updates should occur in an after trigger. See Triggers:

There are two types of triggers:

  • Before triggers are used to update or validate record values before they’re saved to the database.
  • After triggers are used to access field values that are set by the system (such as a record's Id or LastModifiedDate field), and to affect changes in other records, such as logging into an audit table or firing asynchronous events with a queue. The records that fire the after trigger are read-only.
7
  • 2
    Just commenting for extra emphasis that the Null Pointer Exception is due to attempting to work with related data from a trigger context variable. If you're using Trigger.new, Trigger.newMap, Trigger.old, or Trigger.oldMap, and your field reference has more than one dot/period/full-stop (like aic.Case__r.Status, which has 2), you're gonna have a bad time.
    – Derek F
    Commented May 1, 2017 at 17:42
  • @DerekF Right. I edited my post to try to clarify that a bit.
    – Adrian Larson
    Commented May 1, 2017 at 17:49
  • Thanks @AdrianLarson this makes sense. I do have a follow up question on the Map. I am getting error: DML requires SObject or SObject list type: Map<Id,Case>. When I dig into it I keep finding that Sets are not available for DML. What would you suggest?
    – Olivia
    Commented May 1, 2017 at 18:24
  • @Olivia Bah I though I remembered to include the .values() call but apparently still managed to leave it out. You use a Map to de-duplicate, then get the List by calling .values() and passing that collection to your update call.
    – Adrian Larson
    Commented May 1, 2017 at 18:25
  • @AdrianLarson no worries, this is working with the .values() call. I am still uncertain how to make this recognizable ->if(record.Case__r.Status == 'Sent to Accounting'). Should I do something like, query for all Cases related to the current page Id, outside of the loop? and then wrap the conditional around the entire loop?
    – Olivia
    Commented May 1, 2017 at 19:10
0

You should query for the relationship object field values. Assuming, Accounting_Instructions__c is parent for your case object, try the following:

trigger CompletedAccountingInstructions on Accounting_Instructions__c (before update) {
    List<case> caseList = [select id, status, casenumber from case where accounting_Instructions__r IN:Trigger.new];

    for(case cs:caseList){
    cs.status = 'open';
    }

    /*for(Accounting_Instructions__c aic : Trigger.new) {
        //aic.Case__r.Status = 'Open'; this seems like the easiest path but I receive a Null Pointer Exception when I do it this way
    }*/
    Accounting_Instructions__c[] instructions = [SELECT Id, Accounting_Complete__c, Case__r.Status FROM Accounting_Instructions__c WHERE Case__r.Status = 'Sent To Accounting' AND Accounting_Complete__c = TRUE AND Id IN :Trigger.new];

    for(Accounting_Instructions__c aic : instructions){
        aic.Case__r.Status = 'Open';
    }
}

Change the relationship accordingly in the query if its different.

2
  • The OP sure makes it look like Case is the parent, not child.
    – Adrian Larson
    Commented May 1, 2017 at 17:25
  • Yes, the Case is the Parent. So unfortunately I can't query for accounting_Instructions__r .
    – Olivia
    Commented May 1, 2017 at 17:33

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .