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
Josh Davis 47Josh Davis 47 

After insert from APEX no record is actually created even though debug log says it was

Hey Everyone, I am having an issue with some code and can't find the answers I'm looking for, hopefully someone else has ran into this before.
Issue: Even though the code inserts the 'AsyncRequests__c' records and I can see in the debug log that they are inserted.  There is nothing in Salesforce that shows that they were.  If I go to the URL with the ID as specified it says 'Unfortunately, there was a problem. Please try again. If the problem continues, get in touch with your administrator with the error ID shown here and any other related details. The requested resource does not exist', there is no DML operations indicating deletion, there are no new records visible in the UI or anything in the recycling bin.  It is interesting to note that in my various tests

Trigger that Calls the Class, it is very simple and for the problem I am describing can be summed up by saying that 'Trigger.new' gets dumped into 'ContactList' used by the class.
trigger AsyncNPICall on Contact (after insert, after update) {
    AsyncProcessing.handleNPITrigger(trigger.new, trigger.newMap,
        trigger.oldMap, trigger.operationType);
}

Apex Class:
public class AsyncProcessing {

	// Simple protection from workflows and triggers
	private static Boolean alreadyProcessed = false;	

	public static void handleNPITrigger(List<Contact> ContactList, Map<ID, Contact> newMap, Map<ID, Contact> oldMap, TriggerOperation operation) {
		if(alreadyProcessed) return;
		alreadyProcessed = true;

		List<AsyncRequest__c> newAsyncRequests = new List<AsyncRequest__c>();
		List<String> textChangedIds = new List<ID>();

		System.debug('Total Contacts in Contact List: ' + ContactList.size());
		for(Contact co: ContactList)
		{
			// If the record is new and NPI != blank or if the NPI has changed
			if(operation == TriggerOperation.AFTER_INSERT && !String.isBlank(co.NPI__c) || co.NPI__c!= oldMap.get(co.id).NPI__c && !String.isBlank(co.NPI__c)) {
				textChangedIds.add(co.id); 
			}
			// Once 99 records have been looped, group them for Asynchronus Processing, join their IDs into a long text field on the custom object 'AsyncRequest__c'
			if(textChangedIds.size()>99) {
				newAsyncRequests.add(
					new AsyncRequest__c(
						AsyncType__c = 'NPI API Call',
						Params2__c = string.Join(textChangedIds,',')
					)
				);
				System.debug('Current Async Request Size in Loop: '+newAsyncRequests.size());
				textChangedIds.clear();
			}
		}
			// Whatever is left over after processing all the records if the amount never reached 99, create a final 'AsyncRequest__c' record to house them
		if(textChangedIds.size()>0){
			newAsyncRequests.add(
				new AsyncRequest__c(
					AsyncType__c = 'NPI API Call',
					Params2__c = string.Join(textChangedIds,',')
				)
			);
		}
		// System Debugging to ensure all values are present
		System.debug('Async Request Size:' + newAsyncRequests.size());
		for( AsyncRequest__c a: newAsyncRequests){
			System.debug(a.Name + a.params2__c);
		}

		insert(newAsyncRequests);
		
		// Debugging the final insert of the records
		for (AsyncRequest__c ar : newAsyncRequests) {
			System.debug(ar.id);
		}
	}
}

Debug Log:
User-added image

The final oddity in all of this is that even though there is no trace of the inserted records, the autonumbering in Salesforce does skip like the records were inserted.

I am at a loss for why this would be happening and wondering if someone might be able to point me in the right direction of what I am missing. Thanks in advance!
SPrajapatiSPrajapati
It looks like some error is occurring after the record is inserted and the transaction is getting rolled back. Please check the whole debug log for any errors.
Josh Davis 47Josh Davis 47
There is no other error in the debug log, and if it gets rolled back, wouldn't it never have used the auto number and they would have continued sequentially?  I have included a screenshot below to show you what I mean.  For my own sanity check, I will move the code to a scratch org to test that only has my code to check that.  There is nothing else that is showing by way of error in my current environment.

User-added image
Abhishek BansalAbhishek Bansal
Hi Josh,

Can you please check if you have any triggers running on the AsyncRequest__c object? The only possible case is that records are somwhow deleted from the triggers running on this object.
If this also looks fine then I dont see any reason why the records are deleted.

The other reason might be that the user fro which you are trying to view these newly created records do not have the permission to view them. Please check the access on the object as well.

Let me know if you find anything on this. I am also available to connect on a call in case this is urgent.

Thanks,
Abhishek Bansal.
Gmail: abhibansal2790@gmail.com
Skype: abhishek.bansal2790
Phone: +917357512102
vishal-negandhivishal-negandhi

Hi Josh, 

In your main org / production where you are unable to access these records from UI, what happens if you try to run a report or SOQL in dev console? 

Josh Davis 47Josh Davis 47
Thank you for the responses so far but neither have turned anything up.  Object has View All and Public/Read Write for sharing rules, when I ran the query it does not show any of the other records.  I am going to move the code to scratch org to check if I see the same issue, if I do, I will post a link to the GitHub so anyone could download it to see if it gives any clues.

User-added image
Josh Davis 47Josh Davis 47
I can confirm that in a scratch org the records get created correctly with no issues.  I have no triggers on the object in question so I am not sure what is going on there but will continue to review and post what I find here.
Josh Davis 47Josh Davis 47
On further review I was mistaken on the insert in the scratch org, although it did work correctly it was because the file that was imported did not have a duplicate record according to the matching rules.  You'll see in the debug log below that the contact list size is '199', this was because one of the records matched according to duplicate rules in Salesforce and so was not imported, but this is where I have found the issue to be originating from.  If the batch does not have any issues with the duplicate rule errors, then it imports successfully.  If there are duplicate errors in the batch, then it has the appearance of a successful import (as shown below) but is ultimately rolled back.

Leads me to my Next Question: Why does having a single record in batch that has a problem with duplicate rules cause only the AsyncRequest__c records from being rolled back for that batch?  The contacts get inserted correctly (all except the one) so there is no issue there, but only the AsyncRequest__c records that were created from the batch get rolled back.  Is there a way for me to catch this rollback in a system.debug of somekind?  I can reliably replicate it, but I can't see it in any debug statement anywhere or in any error.

 User-added image
Current Code for my AsyncProcessing class (change is at the end by the insert where I have added more system.debug statements)
public class AsyncProcessing {

	// Simple protection from workflows and triggers
	private static Boolean alreadyProcessed = false;	
	

	public static void handleNPITrigger(List<Contact> ContactList, 
		Map<ID, Contact> newMap, Map<ID, Contact> oldMap, 
		 TriggerOperation operation)
	{
		if(alreadyProcessed) return;
		alreadyProcessed = true;


		List<AsyncRequest__c> newAsyncRequests = new List<AsyncRequest__c>();
		List<String> textChangedIds = new List<ID>();

		System.debug('Total Contacts in Contact List: ' + ContactList.size());
		for(Contact co: ContactList)
		{
			if(operation == TriggerOperation.AFTER_INSERT && !String.isBlank(co.NPI__c) || co.NPI__c!= oldMap.get(co.id).NPI__c && !String.isBlank(co.NPI__c)) {
				textChangedIds.add(co.id); 
			}
			
			if(textChangedIds.size()>99) {
				newAsyncRequests.add(
					new AsyncRequest__c(
						AsyncType__c = 'NPI API Call',
						Params2__c = string.Join(textChangedIds,',')
					)
				);
				System.debug('Current Async Request Size in Loop: '+newAsyncRequests.size());
				textChangedIds.clear();
			}
		}

		if(textChangedIds.size()>0){
			newAsyncRequests.add(
				new AsyncRequest__c(
					AsyncType__c = 'NPI API Call',
					Params2__c = string.Join(textChangedIds,',')
				)
			);
		}
		System.debug('Async Request Size:' + newAsyncRequests.size());
		for( AsyncRequest__c a: newAsyncRequests){
			System.debug(a.Name + a.params2__c);
		}

		Database.SaveResult[] srList = database.insert(newAsyncRequests,false);
		system.debug(srList+'srList+++');
		for (Database.SaveResult sr : srList) {
		if (sr.isSuccess()) {
			// Operation was successful, so get the ID of the record that was processed
			System.debug('Successfully inserted AsyncRequest__c. AsyncRequest__c ID: ' + sr.getId());
		}else {
			for(Database.Error err : sr.getErrors()) {
				System.debug('The following error has occurred.');                    
				System.debug(err.getStatusCode() + ': ' + err.getMessage());
				System.debug('Error Ids: ' + err.getFields());
        	}
		}
		}


		for (AsyncRequest__c ar : newAsyncRequests) {
			System.debug(ar.id);
		}
	}

}

My only Trigger on the AsyncRequest__c object
trigger OnAsyncRequestInsert on AsyncRequest__c (after insert) 
{
    if(Limits.getLimitQueueableJobs() - Limits.getQueueableJobs() > 0)
    try
    {
        QueueableAsyncProcessing.enqueueQueueableAsyncProcessing(null);
    } catch(Exception ex)
    {
        
    }
}


My only other class that performs an update action against the contacts (at the very end)
public without sharing class QueueableAsyncProcessing 
	implements queueable, Database.AllowsCallouts {

 	public void execute(QueueableContext context)
    {
    	if(!AppCustomSetting.appEnabled) return; // On/off switch
    	List<AsyncRequest__c> requests;
    	try
    	{
	    	requests = [Select ID, AsyncType__c, Params2__c
	    		FROM AsyncRequest__c 
	    		where Error__c = false And
	    		CreatedById = :UserInfo.getUserId() 
	    		Limit 1 for update];
    	}
    	catch(Exception ex) { return; }
    	if(requests.size()==0 ) return;
    	
    	AsyncRequest__c currentRequest = requests[0];
    	
    	try
    	{
    		if(currentRequest.AsyncType__c=='NPI API Call') 
				npiAPICall(currentRequest);
    		
    		System.debug(currentRequest);
			currentRequest.Error__c = true;
			currentRequest.Error_Message__c = 'I Ran Succesfully';

			Update currentRequest;

			// delete currentRequest;
			// database.emptyRecycleBin(new List<ID>{currentRequest.id});
    		
    	}
    	catch(Exception ex)
    	{
    		currentRequest.Error__c = true;
    		currentRequest.Error_Message__c = ex.getMessage();
    		update currentRequest;
    	}

    	List<AsyncRequest__c> moreRequests = [Select ID, AsyncType__c, Params2__c 
    		FROM AsyncRequest__c 
    		where Error__c = false 
    		and ID <> :currentRequest.id 
    		and	CreatedById = :UserInfo.getUserId() 
    		Limit 1 ];
    	
    	if(moreRequests.size()==0) return;
    	
		try
		{
			enqueueQueueableAsyncProcessing(context.getJobId());
		}
		catch(Exception ex)
		{
			tryToQueue();
		}
		
    }

	public static void enqueueQueueableAsyncProcessing(ID currentJobId)
	{
		List<AsyncApexJob> jobs = [Select ID, Status, ExtendedStatus from AsyncApexJob 
					where JobType = 'Queueable' And (status='Queued'  Or Status='Holding') 
					and CreatedById = :userinfo.getUserID() and 
					ApexClass.Name='QueueableAsyncProcessing' and ID!= :currentJobId Limit 1 ];
		if(jobs.size()==1) return;	// Already have one queued that isn't this one.
		
		system.enqueueJob(new QueueableAsyncProcessing());
	}

    
    @future
    private static void tryToQueue()
    {
    	if(!AppCustomSetting.appEnabled) return; // On/off switch
    	try
    	{
			if(Limits.getLimitQueueableJobs() - Limits.getQueueableJobs() > 0)
				enqueueQueueableAsyncProcessing(null);
    	}
    	catch(Exception ex)
    	{

    	}
    }

    public void npiAPICall(AsyncRequest__c request)
    {
    	Integer allowedCallouts = 
    	Limits.getLimitCallouts() - Limits.getCallouts();
		if(allowedCallouts<=0) return;
		
		List<ID> idsAfterSplit = request.Params2__c.split(',');
		
		List<Contact> contacts= [SELECT Id, NPI__c, SubSpecialty__c FROM Contact WHERE Id IN :idsAfterSplit LIMIT :allowedCallouts];
			for(Contact c : contacts){
				if(c.npi__c.length() == 10 ){
					Boolean FoundPrimary = False;
					string NPI = 'https://npiregistry.cms.hhs.gov/api/?version=2.1&number='+ c.NPI__c;
			
					HttpRequest req = new HttpRequest();
					req.setEndpoint(NPI);
					req.setMethod('GET');
			
					Http http = new Http();
					HttpResponse res = http.send(req);
					string updatedresponse = res.getBody().replace('"desc":', '"desc_Z":');
					System.debug('resbody:'+updatedresponse);
					JSON2Apex response = (JSON2Apex)JSON.deserialize(updatedresponse, JSON2Apex.Class);
					System.debug(response);
			
					if(response != null && response.result_count == 1){
						for(JSON2Apex.Taxonomies t: response.Results[0].taxonomies){
							if(t.primary) {
								c.SubSpecialty__c = t.desc_Z;
								FoundPrimary = true;
								break;
							}
						}
						if(!FoundPrimary) {
							c.SubSpecialty__c = response.Results[0].taxonomies[0].desc_Z;
						}
						
					}

					else {
						c.SubSpecialty__c = 'Error: No Match for NPI Number';
					}
				}

				else {
					c.SubSpecialty__c = 'Error: NPI Number Incorrect Length';
				}

			}
		database.update(contacts, false);
	}
	
}