0

Im having some issues updating an account after a future callout. It is worth noting that the POST from Salesforce to the ERP is successful. The issue is regarding the update after the callout.

Errors:

  • 21:05:35:444 EXCEPTION_THROWN [11]|System.AsyncException: Future method cannot be called from a future or batch method: POSTCliente.doGet(Id, String, String)
  • System.DmlException: Update failed. First exception on row 0 with id 0017800000GEakXAAT; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, POSTClienteTrigger: execution of AfterUpdate

Any help is greatly appreciated! This is my code:

Main Class:

global class POSTCliente {

    @future(callout=true) public static void doGet(Id accountId, String requestMethod, String endpoint){
        baseSignature oAuth = new baseSignature();
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setMethod(requestMethod);
        req.setEndpoint(endpoint);      
        String header = oAuth.sign(req);
        req.setHeader('Authorization', header);
        req.setHeader('Content-Type', 'application/json;charset=UTF-8');
        req.setHeader('Accept', '*/*');
        req.setHeader('Accept-Language', 'es');
        req.setHeader('Prefer', 'Transient');
        req.setTimeout(120000);
        
        Account newAccount = [Select Id, RecordTypeId, id__c, LastName, Email__c, Phone, OwnerId, FirstName, PersonEmail, PersonMobilePhone, Fecha_de_ltima_sincronizaci_n__c, Resultado_general_de_sincronizaci_n__c FROM Account WHERE Id =: accountId LIMIT 1];
        User accountOwner = [SELECT Id, ID_Netsuite__c FROM User Where Id =: newAccount.OwnerId];
        String personphone = newAccount.PersonMobilePhone;
        String ownerNetsuiteId = accountOwner.ID_Netsuite__c;
        String personemail = newAccount.PersonEmail;
        //String accountName = newAccount.Name;
        JSONGenerator jg = JSON.createGenerator(false);
        if(newAccount.RecordTypeId == '0128a000000djPkAAI'){
            jg.writeStartObject();
            jg.writeStringField('firstname', newAccount.FirstName);
            jg.writeStringField('lastname', newAccount.LastName);
            jg.writeStringField('custentity_tipopersona', '1');
            jg.writeStringField('email', personemail);
            //jg.writeStringField('id', newAccount.id__c);
            jg.writeBooleanField('isperson', TRUE);
            jg.writeBooleanField('isinactive', FALSE);
            jg.writeStringField('phone', personphone);
            jg.writeStringField('salesrep', ownerNetsuiteId);
            jg.writeEndObject();
        }else if (newAccount.RecordTypeId == '0128a000000djPfAAI'){
            jg.writeStartObject();
            jg.writeStringField('firstname', 'Salesforce:');
            //jg.writeStringField('lastname', accountName);
            jg.writeStringField('custentity_tipopersona', '1');
            jg.writeStringField('email', newAccount.Email__c);
            jg.writeBooleanField('isperson', FALSE);
            jg.writeBooleanField('isinactive', FALSE);
            jg.writeStringField('phone', newAccount.Phone);
            jg.writeStringField('salesrep', accountOwner.ID_Netsuite__c);
            jg.writeEndObject();
        }
        
        String reqBody = jg.getAsString();
        req.setBody(reqBody);
        System.debug('Request body set to: '+req.getBody());
        HttpResponse res = new HttpResponse();
        res = h.send(req);
        String resBody = res.getBody();
        String idNetsuiteURL = '';
        String idNetsuite = '';
        string[] headerkeys = res.getHeaderKeys();
        Map<string, string> headers = new map<string, string>();
        for(string s : headerkeys){
            headers.put(s,res.getHeader(s));
            system.debug('header: ' + s + ' value: ' + res.getHeader(s));
            if(s.equalsIgnoreCase('Location')){
                idNetsuiteURL = res.getHeader(s);
                System.debug(idNetsuiteURL);
                idNetsuite = idNetsuiteURL.right(5);
                System.debug(idNetsuite);
            }
        }
        System.debug('Response Body: ' + res.getBody());
        if(res.getStatusCode() == 204){
            newAccount.Id = accountId;
            newAccount.Fecha_de_ltima_sincronizaci_n__c = System.Datetime.now();
            newAccount.Resultado_general_de_sincronizaci_n__c = 'EXITO';
            newAccount.id__c = idNetsuite;
            update newAccount;
            System.debug('Actualicé la cuenta');
        }   
    }
}

Trigger

trigger POSTClienteTrigger on Account (after insert, after update) {
    for(Account cliente : Trigger.new){
        Id acctId = cliente.Id;
        String rMethodPOST = 'POST';
        String rMethodPATCH = 'PATCH';
        String endpointPOST = 'https://4624785.suitetalk.api.netsuite.com/services/rest/record/v1/customer';
        String endpointPATCH = 'https://4624785.suitetalk.api.netsuite.com/services/rest/record/v1/customer/' + cliente.id__c;
        if(Trigger.isInsert){
            POSTCliente.doGet(acctId, rMethodPOST, endpointPOST);
        }if(Trigger.isUpdate){
           POSTCliente.doGet(acctId, rMethodPATCH, endpointPATCH); 
        }
    }
}

1 Answer 1

1

You're causing trigger recursion. You've got this sequence of execution happening:

  • Account trigger event (insert or update) goes off.
  • future method is enqueued.

--- transaction commits ---

  • future method is executed and performs callout
  • future method runs update DML on Account
  • Account trigger event (insert or update) goes off.
  • future method is enqueued.
  • Exception is thrown, because you cannot enqueue a future method from a future method.

A typical way to handle this is to move your logic into a handler class (which is a best practice anyway!) and use a static Boolean variable to signal to the handler that an update is coming from a callout - meaning that it doesn't need to set up another callout. That looks roughly like this:

class AccountTriggerHandler {
    public static Boolean inCalloutHandler = false;
    
    public void onUpdate(List<Account> newList) {
        if (!inCalloutHandler) {
            // enqueue future methods here
        }
    }
}

Then in your future method:

    @future(callout=true) public static void doGet(Id accountId, String requestMethod, String endpoint){
        // snipped.

        if(res.getStatusCode() == 204){
            newAccount.Id = accountId;
            newAccount.Fecha_de_ltima_sincronizaci_n__c = System.Datetime.now();
            newAccount.Resultado_general_de_sincronizaci_n__c = 'EXITO';
            newAccount.id__c = idNetsuite;
            AccountTriggerHandler.inCalloutHandler = true;
            update newAccount;
            AccountTriggerHandler.inCalloutHandler = false;
            System.debug('Actualicé la cuenta');
        }   
    }

Note that you set the Boolean to true only for the single line where you perform the update, and then reset it afterwards; this ensures that any other code that might execute in the transaction gets the expected behavior. That extra care doesn't make a difference here, where you're at the end of your logic anyway, but it's generally a good practice when working with this type of switch.


Your code does need attention for a number of other best practices, including:

  • You hard-code a record type Id; use schema methods instead to retrieve it dynamically.
  • You're enqueuing one future method per Account, which will cause you to throw limit-related exceptions. You can only enqueue 50 future methods per transaction. You should work on bulkifying your code by either sending multiple records to the endpoint in a single call, or by making multiple callouts in a single future method.
  • future methods are an old technology and do not support chaining. You should consider whether a Queueable might let you handle this more effectively by chaining to work through a list of records.
  • You're using JSONGenerator and it doesn't appear that you need to do so. See our canonical QA for how to use a simpler approach.
1
  • Thank you very much David, it worked! You are very knowledgeable.
    – TOSB
    Dec 13, 2022 at 3:47

You must log in to answer this question.

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