3

I'm working on an app that integrates with an external web service, so saving a record in SF will fire an insert trigger that sends the new record data to the web service.

We're working with 3 custom objects:

Invoice (main object) - required
--------- Line (child of Invoice) - required
--------------- Revenue Entry (child of Line) - optional

The trigger is connected to the Invoice object.

Since we're using JS remoting for the insert, the trigger ends up firing before the related records are inserted, leading to an incorrect or failed API request.

Is there a way to delay the trigger from firing until all related records are inserted?

As I write this question, one workaround that comes to mind is to create another object whose record is inserted only after all the required records are in. This object can capture the main record's id which can be used to query all the data.

Are there any other (hopefully more elegant) ways around this problem?

  • Sounds like the problem might be with the trigger; if the children are optional why would the trigger fail when the child records are not present? – Mike Chale Sep 9 '13 at 17:04
  • Sorry, I misspoke. Line is actually required (recent change), but Revenue Entry is optional. – geeljire Sep 9 '13 at 17:19
1

Create a web service method, and have your client code call that method. Use a static variable to keep your trigger from firing when using this web service.

global class WebServices {
    static webservice String createInvoice(InvoiceData data) {
        String result;
        try {
            executeTrigger = false;
            // Insert Invoice
            // Insert Line Item
            // Insert Revenue
        } catch(exception e) {
            result = 'failed: '+e.getMessage();
        }
        return result; // 
    }

    global class InvoiceData {
        // describe invoice data
    }

    global static boolean executeTrigger = true; // for non-ws updates
}

Edit: You could also use REST instead of SOAP; the end result is the same.

1

Why not just make one more call from your client code to kick off the webservice instead of doing it through the insert trigger?

Ideally - the call would be through your client code - but if you are set on doing it through apex, just move the webservice apex code into its separate webservice class and call that after all your inserts. More information here: http://wiki.developerforce.com/page/Apex_Web_Services_and_Callouts

0

Will you, at the time of inserting Invoice record, know if there are any child records to be inserted?

How about having 'Inserted any child records' flag on the invoice and make the trigger fire off of it. The flag itself can be set by time based workflow after 30 mins of record creation (considering any child records will be entered by then). Or, use another trigger/roll-up summary to set it when appropriate.

  • Thanks Sai. Data for all the records is captured by one form, so there is perhaps 500ms - 1s delay between the trigger firing and the insert of related records. I will know if there are child records before inserting the invoice record. The trigger/workflow solution did occur to me, but I'd like to use an insert trigger if possible since I already have a somewhat complex update trigger in place for real updates. – geeljire Sep 9 '13 at 17:13
0

Create a utility class to process invoices.

public class InvoiceUtil {
    public static void processInvoices(Invoice__c[] scope) {
        // ...
    }
}

Create a field on Invoice to control if the trigger executes:

trigger insertInvoice on Invoice__c (after insert) {
    Invoice__c[] processNow = new Invoice__c[0], processLater = new Invoice__c[0];
    for(Invoice__c record:Trigger.new)
         (record.ProcessNow__c?processNow:ProcessLater).add(record);
    InvoiceUtil.processInvoices(processNow);
}

Create a trigger on Revenue as well to control the logic from there:

trigger insertRevenue on Revenue__c (after insert) {
    Map<Id, Invoice__c> invoices = new Map<Id, Invoice__c>();
    for(Revenue__c record:Trigger.new)
        invoices.put(record.Invoice__c, null);
    invoices.putAll([SELECT Id, Field1__c, Field2__c, Etc__c FROM Invoice__c WHERE Id IN :invoices.keyset()]);
    InvoiceUtil.processInvoices(invoices.values());
}

Of course, if there's more than 200 Revenue__c, you'll still get duplicate updates.

  • What if there is no Revenue record? – geeljire Sep 14 '13 at 21:59
  • That's why we create a trigger that runs on both objects. ProcessNow__c is a checkbox that is either set (true) or clear (false). Your code can then dictate if the invoice logic should be called, because there is no revenue to enter, or not called, because the revenue code will instead take care of it. – sfdcfox Sep 14 '13 at 22:56

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.