1

I have written a trigger for purchases if the purchase contains several of the same purchase detail items they should be summed up and merged as 1 purchase detail. When I use a before insert I get in a loop because the inserts are checked again. I used a flag to stop this but this doesn't work. What am I missing?

Trigger

trigger AankoopDetailTrigger on Aankoop_Detail__c (before insert) {
    if(Trigger.isBefore) {
        // Process before insert
        if( Trigger.isInsert){
            AankoopDetailTriggerHandler.isBeforeInsert(trigger.new);
            if(AankoopDetailTriggerHandler.aankRecursion)
                return;
            AankoopDetailTriggerHandler.aankRecursion = true;
        }
    }
}

Triggerhandler

public class AankoopDetailTriggerHandler {
    public static boolean aankRecursion;
    static {
        aankRecursion = false;
    }
    //before insert
    public static void isBeforeInsert(List<Aankoop_Detail__c> aankoopdetailList){
        Map<String, Decimal> amounts = new Map<String, Decimal>();
        for (Aankoop_Detail__c purchase : aankoopdetailList)
        {
            if (!amounts.containsKey(purchase.Aankoop__c ))
                amounts.put(purchase.Aankoop__c , purchase.Prijs__c );
            else
                amounts.put(purchase.Aankoop__c , amounts.get(purchase.Aankoop__c ) + purchase.Prijs__c );
        }
        List<Aankoop_Detail__c> records = new List<Aankoop_Detail__c>();
        for (String name : amounts.keySet())
            records.add(new Aankoop_Detail__c(Aankoop__c=name, Prijs__c =amounts.get(name)));
        insert records;
    }
}

Test

@isTest
public class AankoopDetailTriggerHandlerTest{

    @testSetup
    public static void test_Data(){
        Winkel__c shop = new Winkel__c(name='name' , Store_Id__c  = 007 , Winkelnaam__c = 'Shopname');
        insert shop;
        Aankoop__c buy = new Aankoop__c(name='buy' , Winkel__c  = shop.id);
        insert buy;
        Aankoop_Detail__c  buydet1 = new Aankoop_Detail__c(name = 'det-001' , Aankoop__c = buy.id);
        insert buydet1;
        Aankoop_Detail__c  buydet2  = new Aankoop_Detail__c(name = 'det-001' , Aankoop__c = buy.id);
        insert buydet2;
    }
    @isTest
    public static void test_Method_1(){
        system.assertEquals(1,[SELECT id FROM Aankoop_Detail__c].size());
    }
}
  • 2
    Just as a general comment, static boolean recursion guards like yours will break if/when you try to insert more than 200 records. Generally speaking, the next logical progression would be to use a static Set<Id> (to track individual Ids that you've processed already). That's a bit difficult in a before insert trigger, however, as your records won't yet have an Id at that point. Just something to be aware of. – Derek F Jul 27 '18 at 18:38
6

You're seeing recursion because your recursion guard is in the wrong place. When this gets executed:

        AankoopDetailTriggerHandler.isBeforeInsert(trigger.new);
        if(AankoopDetailTriggerHandler.aankRecursion)
            return;
        AankoopDetailTriggerHandler.aankRecursion = true;

what happens is something like this:

  • Insert goes off
  • before insert trigger executed
  • isBeforeInsert called.
  • insert DML occurs inside isBeforeInsert.
  • before insert trigger executed.
  • isBeforeInsert called.
  • insert DML occurs inside isBeforeInsert.

and so on until the limit goes off. Your recursion-guard Boolean is never set and never checked, because you don't touch it until after the recursion takes place - meaning it never actually executes.

The direct fix to this is placing the trigger handler invocation last:

        if(AankoopDetailTriggerHandler.aankRecursion)
            return;
        AankoopDetailTriggerHandler.aankRecursion = true;
        AankoopDetailTriggerHandler.isBeforeInsert(trigger.new);

That fixes the recursion issue, but it doesn't result in the trigger doing what you want. Your unit test case:

@isTest
public static void test_Method_1(){
    system.assertEquals(1,[SELECT id FROM Aankoop_Detail__c].size());
}

is still going to fail: that SELECT will return 3, because you've got the two records you inserted in your @testSetup method, plus another that you inserted in your trigger handler. Your handler doesn't do anything to impede those records from being inserted in the first place.

Additionally, your test class isn't populating the Prijs__c field, so you'll have a problem when you try to do your arithmetic.

There isn't a good way to transform data that's being inserted like this inside the trigger itself. I would recommend firing a Queueable from the trigger to handle this cleanup in a separate transaction by inserting the new records and deleting the original duplicates, which seems to be what you want to do.

Otherwise, you'd need to addError() on the original duplicate records, but that will result in an exception and a rollback of the transaction in most cases. (Unless the error's caught and handled elsewhere in the call stack, or the caller used Database.insert(myList, false) to allow partial success. That's the only way for your trigger to impede the insert of the duplicates like this.

3

You need to check before calling the rest of your handler:

if(AankoopDetailTriggerHandler.aankRecursion)
    return;
AankoopDetailTriggerHandler.aankRecursion = true;
AankoopDetailTriggerHandler.isBeforeInsert(trigger.new);

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.