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
Kelly KKelly K 

Upsert not working on cloned opp

I haven't seen this before and I'm having trouble figuring out why this isn't working. I have a class that handles adding OpportunityContactRoles to an opportunity on create, on account Id change, and on close of an opportunity - the test class for that passes, no issues has been working great for a few years.

I was working on new code that involves updating the opportunity record type, owner, and a few other fields and in some cases cloning the opportunity. My test class for this is failing. The error I'm getting is

System.DmlException: Update failed. First exception on row 0 with id 006180000023YfvAAE; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, Upsert failed. First exception on row 0; first error: INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY, insufficient access rights on cross-reference id: []: []: []

If I turn off the OpportunityContactRoles class I mentioned, my new test class passes 100%. So I've added comments all the way through the contact roles class and I've found that once it hits the upsert statement is what is causing this to happen. 

Is there any particular reason why an opportunity being created using the .clone() method would cause issues with opportunityContactRoles?

Opportunity clonedOpp = opportunity.clone(false, false, true, false);
Best Answer chosen by Kelly K
jp1234jp1234
No, actually source of that null object is exactly that map.  You are trying to looping through that map by calling values() method on that map.  But because it is null (and not empty map), you are getting null object error.  I think that insufficient access rights on cross-reference id is separate issue, though.

All Answers

jp1234jp1234
I am not sure if this would be the issue, but are you setting clonedOpp's ID to false after clone?  Even when I have set opt_preserve_id flag to false (as you have done there), I noticed that sometimes the ID of the original is still copied over.  
Kelly KKelly K
Hi JP,

The issue isn't the Id of the opportunity, it's the Id of the opportunity contact role from the other class. If I comment out the call for the contact roles class in my after insert trigger, my test class for the new code passes fine. 

Also - this is the original error: Update failed. First exception on row 0 with id 006180000023YkqAAE; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, Attempt to de-reference a null object: []: []

The error I have listed above I had tried breaking the upsert into insert/update to see if that had any effect (it didn't).

I tried creating the opportunity manually instead of a clone to see if that resolves the error and it doesn't.

Just not sure why when I insert the opportunity record that the contact role upsert is throwing an error about a null contact role Id.

 
jp1234jp1234
I see.   Can you share some of the code snippet and the output of System.debug statement on the contact role object just before the upsert, and what is the exact error message you get when you enclose the upsert operation within try/catch block and printing out the error message using System.debug(ex.getMessage()); statement?  There are so many different possibilities that would result in that error, so it's difficult to diagnose based on the information provided here.
Kelly KKelly K

I know - I've already spent well over 12 hours getting to the core of the issue. So here's what I got. I'm sharing the opportunityaddcontactroles class and 3 other classes. The oppportunityPrequalOppConversion (this is runs in a before insert/update context),  the OpportunityPrequalOppConvCreate (runs in after insert/update context), the OpportunityAddContactRoles (after insert/update context - excuse the comments), and the OpportunityPrequalOppConversionTest. At the end, I have a screenshot of the log and the error message.

 

public with sharing class OpportunityPrequalOppConversion {

	public static void convertPrequalifiedOpp(Map<Id, Opportunity> opportunitiesMap) {
		if(TriggerHandler.firstRunOpportunityPrequalOppConversion) {
			system.debug('Prequal opp method entered');
			Map<Id, Account> accountsMap = getAccountInfo(opportunitiesMap);
			Map<String, Id> userMap = getUserMap();

			Decimal numberOfDocs;
			Id outsideOwnerId;
			Id patientpayOwnerId;

			for(Opportunity opportunity : opportunitiesMap.values()) {
				numberOfDocs = accountsMap.get(opportunity.AccountId).Billable_Docs_Original__c;
				outsideOwnerId = userMap.get(accountsMap.get(opportunity.AccountId).Outside_Sales_Territory__c);
				patientpayOwnerId = userMap.get(accountsMap.get(opportunity.AccountId).Patient_Pay_Territory__c);

				if(opportunity.Business_Interest__c == 'Clearinghouse' || opportunity.Business_Interest__c == 'Clearinghouse & Patient Payment') {
					if(numberOfDocs <= 3) 
						opportunity.RecordTypeId = '0123000000143iO';
					else 
						opportunity.RecordTypeId = '01230000000Jcoh';

					system.debug('biz interest contains CH - new sale (bundle)');
					opportunity.Type = 'New Sale (Bundle)';
					opportunity.OwnerId = outsideOwnerId;
				}
				
				else if(opportunity.Business_Interest__c == 'Patient Payment') {
					system.debug('biz interest is patient payment - new sale (not bundled)');
					opportunity.RecordTypeId = '01213000001RVeU';
					opportunity.Type = 'New Sale (Not Bundled)';
					opportunity.Patient_Payment_Rate_Type__c = 'Standard';
					opportunity.Patient_Payment_Rep__c = patientpayOwnerId;
					opportunity.OwnerId = outsideOwnerId;
				}				
			}

			TriggerHandler.firstRunOpportunityPrequalOppConversion = false;			
		}
	}

	public static Map<String, Id> getUserMap() {
		Map<String, Id> userMap = new Map<String, Id>();

		List<User> userList = [SELECT Id, FirstName, LastName FROM User WHERE IsActive = true and IsPortalEnabled = false];

		for (User user : userList) {
			String fullName = user.FirstName + ' ' + user.LastName;
			userMap.put(fullName, user.Id);	
		}

		return userMap;			
	}

	public static Map<Id, Account> getAccountInfo(Map<Id, Opportunity> opportunityMap) {		
		Set<Id> accountIds = new Set<Id>();
		Map<Id, Account> accountInfo = new Map<Id, Account>();

		for(Opportunity opportunity : opportunityMap.values())
			accountIds.add(opportunity.AccountId);
		
		List<Account> accounts = [SELECT Id, Patient_Pay_Territory__c, Outside_Sales_Territory__c, Billable_Docs_Original__c FROM Account WHERE Id IN :accountIds];

		for(Account account : accounts) 
			accountInfo.put(account.Id, account);
				
		return accountInfo;
	}
}


 
public with sharing class OpportunityPrequalOppConvCreate {
	
	public static void createOppFromPrequalConversion(Map<Id, Opportunity> opportunitiesMap) {
		system.debug('OpportunityPrequalOppConvCreate Method Entered!');
		if(TriggerHandler.firstRunOpportunityPrequalOppConvCreate) {
			List<Opportunity> opportunitiesForInsert = new List<Opportunity>();
			Map<Id, Account> accountsMap = OpportunityPrequalOppConversion.getAccountInfo(opportunitiesMap);
			system.debug('accountsMap  size: ' + accountsMap.size());
			Map<String, Id> userMap = OpportunityPrequalOppConversion.getUserMap();
			system.debug('userMap size: ' + userMap.size());
			Id patientpayOwnerId;
			Id outsideOwnerId;

			for(Opportunity opportunity : opportunitiesMap.values()) {
				patientpayOwnerId = userMap.get(accountsMap.get(opportunity.AccountId).Patient_Pay_Territory__c);
				system.debug('patient pay owner Id: ' + patientpayOwnerId);
				outsideOwnerId = userMap.get(accountsMap.get(opportunity.AccountId).Outside_Sales_Territory__c);
				system.debug('outside owner Id: ' + outsideOwnerId);

				Opportunity clonedOpp = opportunity.clone(false, false, true, false);

				//Opportunity clonedOpp = new Opportunity(AccountId = opportunity.AccountId, Name = 'Patient Payments', StageName = opportunity.StageName, CloseDate = opportunity.CloseDate);

				clonedOpp.Opportunity_ID_SF_External__c = null;
				clonedOpp.RecordTypeId = '01213000001RVeU';
				clonedOpp.Type = 'New Sale (Not Bundled)';
				clonedOpp.Patient_Payment_Rep__c = patientpayOwnerId;
				clonedOpp.OwnerId = outsideOwnerId;
				clonedOpp.Patient_Payment_Rate_Type__c = 'Standard';

				opportunitiesForInsert.add(clonedOpp);
			}			

			system.debug('should insert ' + opportunitiesForInsert.size() + ' record(s)');
			insert opportunitiesForInsert;				
		}
        
        TriggerHandler.firstRunOpportunityPrequalOppConvCreate = false;
	}
}

@isTest
private class OpportunityPrequalOppConversionTest {
	
	static testMethod void convertFromPrequalifiedOpp() {
		User BDRUser = [SELECT Id FROM User WHERE IsActive = true AND ProfileId = '00e30000001Ghun' LIMIT 1];
		User outsideUser = [SELECT Id FROM User WHERE IsActive = true AND ProfileId = '00e30000000oph4' LIMIT 1];

    	Account vendorAccount = new Account(Name = 'Vendor Acct', Status__c = 'Vendor', Type__c = 'Vendor');
    	insert vendorAccount;
    	
    	Go_Forward_PM_System__c goForwardPM = new Go_Forward_PM_System__c (Name = 'Greenway', Status__c = 'Partner', Client_Type__c = 'Channel', 
    		Vendor_Account__c = vendorAccount.Id);
    	insert goForwardPM;
    	
       	Account account1 = new Account(Name='Test', Status__c='Suspect', Type='Medical Practice', Specialty__c='General Practice',
            	Go_Forward_PM_System__c = goForwardPM.Id, Number_of_Docs__c = 4, BillingState='GA');     
       	insert account1;

       	Account account2 = new Account(Name='Test', Status__c='Suspect', Type='Medical Practice', Specialty__c='General Practice',
            	Go_Forward_PM_System__c = goForwardPM.Id, Number_of_Docs__c = 2, BillingState='GA');     
       	insert account2;
    	
		List<Opportunity> oppsOnAcct1 = [SELECT Id FROM Opportunity WHERE AccountId =: account1.Id];
		system.assertEquals(0, oppsOnAcct1.size());
		List<Opportunity> oppsOnAcct2 = [SELECT Id FROM Opportunity WHERE AccountId =: account2.Id];
		system.assertEquals(0, oppsOnAcct2.size());		

		List<Opportunity> oppstoInsert = new List<Opportunity>();

		Opportunity opportunityCHOnly = new Opportunity (AccountId = account1.Id, Name = 'Prequalified: Clearinghouse', StageName = 'Prequalified', CloseDate = System.today(), 
			RecordTypeId = '012a0000001C86U', Business_Interest__c = 'Clearinghouse', BDR_Fix_Accomplish_Avoid__c = 'test', BDR__c = BDRUser.Id, Interest_Source__c = 'Inbound Lead', 
			Turnover_Date__c = System.today(), Discovery_Scheduled_Date__c = System.today());	
		oppstoInsert.add(opportunityCHOnly);
		
		Opportunity opportunityPPOnly = new Opportunity (AccountId = account1.Id, Name = 'Prequalified: Patient Pay', StageName = 'Prequalified', CloseDate = System.today(), 
			RecordTypeId = '012a0000001C86U', Business_Interest__c = 'Patient Payment', BDR_Fix_Accomplish_Avoid__c = 'test', BDR__c = BDRUser.Id, Interest_Source__c = 'Inbound Lead', 
			Turnover_Date__c = System.today(), Discovery_Scheduled_Date__c = System.today());
		oppstoInsert.add(opportunityPPOnly);

		Opportunity opportunityCHandPP = new Opportunity (AccountId = account1.Id, Name = 'Prequalified: Clearinghouse & Patient Payment', StageName = 'Prequalified', CloseDate = System.today(), 
			RecordTypeId = '012a0000001C86U', Business_Interest__c = 'Clearinghouse & Patient Payment', BDR_Fix_Accomplish_Avoid__c = 'test', BDR__c = BDRUser.Id, Interest_Source__c = 'Inbound Lead', 
			Turnover_Date__c = System.today(), Discovery_Scheduled_Date__c = System.today());
		oppstoInsert.add(opportunityCHandPP);

		Opportunity opportunityCHOnly2 = new Opportunity (AccountId = account2.Id, Name = 'Prequalified: Clearinghouse', StageName = 'Prequalified', CloseDate = System.today(), 
			RecordTypeId = '012a0000001C86U', Business_Interest__c = 'Clearinghouse', BDR_Fix_Accomplish_Avoid__c = 'test', BDR__c = BDRUser.Id, Interest_Source__c = 'Inbound Lead', 
			Turnover_Date__c = System.today(), Discovery_Scheduled_Date__c = System.today());	
		oppstoInsert.add(opportunityCHOnly2);

		//Opportunities need to be created by the BDR otherwise the Outside Sales Rep will not be able to transfer to another rep due to our transfer role validation
		system.runAs(BDRUser) {
			insert oppstoInsert;
		}

		//Since we're using system.runAs(), we need to create shares for the outside user since we have criteria based sharing on the opportunity
		insert new OpportunityShare(OpportunityId = opportunityCHOnly.Id, UserOrGroupId = outsideUser.Id, OpportunityAccessLevel = 'Edit', RowCause = 'Manual');
		insert new OpportunityShare(OpportunityId = opportunityPPOnly.Id, UserOrGroupId = outsideUser.Id, OpportunityAccessLevel = 'Edit', RowCause = 'Manual');
		insert new OpportunityShare(OpportunityId = opportunityCHandPP.Id, UserOrGroupId = outsideUser.Id, OpportunityAccessLevel = 'Edit', RowCause = 'Manual');
		insert new OpportunityShare(OpportunityId = opportunityCHOnly.Id, UserOrGroupId = outsideUser.Id, OpportunityAccessLevel = 'Edit', RowCause = 'Manual');

		oppsOnAcct1 = [SELECT Id FROM Opportunity WHERE AccountId =: account1.Id];
		system.assertEquals(3, oppsOnAcct1.size());		
		oppsOnAcct2 = [SELECT Id FROM Opportunity WHERE AccountId =: account2.Id];
		system.assertEquals(1, oppsOnAcct2.size());			

		List<Opportunity> opportunities = new List<Opportunity>();

		//These need to be reset due to the way that unit tests work (all one transaction)
		TriggerHandler.resetAll();

		//Then the Outside Sales Rep changes the stage
		system.runAs(outsideUser) {
			test.startTest();
				opportunityCHOnly.Name = 'Clearinghouse';
				opportunityCHOnly.StageName = 'Demo Scheduled';
				opportunityCHOnly.Sales_Commit__c = 'Commit';
				opportunityCHOnly.PM_EMR_Status__c = 'Brand New PM/EMR Install';
				opportunityCHOnly.Our_Competition__c = 'Availity';			
				opportunities.add(opportunityCHOnly);

				opportunityPPOnly.Name = 'Patient Payments';
				opportunityPPOnly.StageName = 'Demo Scheduled';
				opportunityPPOnly.Sales_Commit__c = 'Commit';
				opportunityPPOnly.PM_EMR_Status__c = 'Brand New PM/EMR Install';
				opportunityPPOnly.Our_Competition__c = 'Availity';	
				opportunities.add(opportunityPPOnly);

				opportunityCHandPP.Name = 'Clearinghouse and Patient Payments';
				opportunityCHandPP.StageName = 'Demo Executed';
				opportunityCHandPP.Sales_Commit__c = 'Commit';
				opportunityCHandPP.PM_EMR_Status__c = 'Brand New PM/EMR Install';
				opportunityCHandPP.Our_Competition__c = 'Availity';
				opportunities.add(opportunityCHandPP);

				update opportunities;
			test.stopTest();
		}

		oppsOnAcct1 = [SELECT Id FROM Opportunity WHERE AccountId =: account1.Id];
		system.assertEquals(4, oppsOnAcct1.size());
		oppsOnAcct2 = [SELECT Id FROM Opportunity WHERE AccountId =: account2.Id];
		system.assertEquals(1, oppsOnAcct2.size());		
	}
}
 

 

public with sharing class OpportunityAddContactRoles {

 	public static void AddToOpportunities(map<Id, Opportunity> opportunities) {
 		if(TriggerHandler.firstRunOpportunityAddContactRoles) {
	 		system.debug('OpportunityAddContactRoles.AddToOpportunities Method entered');
	 		system.debug('number of opportunities to process: ' + opportunities.size());
			//Set up array for opportunity contact roles and for accountIds
			OpportunityContactRole[] opportunityContactRoles = new OpportunityContactRole[] {};
			list<OpportunityContactRole> deleteOpportunityContactRoles = new list<OpportunityContactRole> {};
		    Id[] accountIds = new Id[] {};

			//Grab the account Ids from the opportunity map and assign them to the array
		    for (Opportunity opportunity : opportunities.values())
		        accountIds.add(opportunity.AccountId);
		
			//Query all contacts with the associated accounts and assign them to a map
		    map<Id, Account> accounts = new map<Id, Account>([SELECT Id, (SELECT Id FROM Contacts) FROM Account WHERE Id IN :accountIds]);
		    system.debug('number of accounts to add contact roles: ' + accounts.size());

			//Reverse mapping - assigns contacts to a map, used later
			map<Id, Contact> contacts = new map<Id, Contact>();
			for (Account account : accounts.values())
				for (Contact contact : account.Contacts)
					contacts.put(contact.Id, contact);
		    
		    //Master map, includes all opportunities by contact by contact role: Mapping = Opportunity.Id { Contact.Id { OpportunityContactRole } }
		    map<Id, map<Id, OpportunityContactRole>> existingOpportunitiesContactRolesByContactId = new map<Id, map<Id, OpportunityContactRole>> ();

		    List<Opportunity> oppList = [SELECT Id, AccountId, (SELECT Id, ContactId FROM OpportunityContactRoles) FROM Opportunity WHERE Id IN :opportunities.keySet()];
			system.debug('size of list of existing contact roles: ' + oppList.size());

		    Integer i = 1;
			//Section checks the opportunities for existing contact roles and adds it to the master map
		    for (Opportunity opportunity : [SELECT Id, AccountId, (SELECT Id, ContactId FROM OpportunityContactRoles) FROM Opportunity WHERE Id IN :opportunities.keySet()]) {	
		    	system.debug('opportunity iteration first for loop: ' + i);
		    	i++;
		    	system.debug('current opportunity Id' + opportunity.Id);

		    	//Singluar map for one opportunity by contacts by contact roles
		    	map<Id, OpportunityContactRole> existingOpportunityContactRolesByContactId = new map<Id, OpportunityContactRole>();    	
		    	
		    	List<OpportunityContactRole> contactRolesSizeCheck = opportunity.OpportunityContactRoles;
		    	system.debug('opportunity contact roles size: ' + contactRolesSizeCheck.size());

		    	//Cycle through the opportunity contact roles for this opportunity and assign them to the map
	    		for (OpportunityContactRole opportunityContactRole : opportunity.OpportunityContactRoles)
	    			existingOpportunityContactRolesByContactId.put(opportunityContactRole.ContactId, opportunityContactRole);
		    		
		    	//Updates the master map by adding the opportunity and the corresponding opportunity roles.
		    	existingOpportunitiesContactRolesByContactId.put(opportunity.Id, existingOpportunityContactRolesByContactId);
		    }

		    i = 1;
			//Section compares existing opportunity roles to contacts within an account and creates the contact role if it doesn't exist or deletes it if it doesn't belong to that account
		    for (Opportunity opportunity : opportunities.values()) {
		    	system.debug('opportunity iteration second for loop: ' + i);
		    	i++;
		    	//Pulls all exsiting contact roles from the map for this current opportunity
		    	system.debug('current opportunity Id: ' + opportunity.Id);
		        if(existingOpportunitiesContactRolesByContactId.containsKey(opportunity.Id))
		        	system.debug('opportunity Id is found in map');
		        else
		        	system.debug('opportunity Id is not found in map -- likely your error here');	
		        //original code:
		    	map<Id, OpportunityContactRole> existingOpportunityContactRolesByContactId = existingOpportunitiesContactRolesByContactId.get(opportunity.Id);

		    	//new code:
		    	/*map<Id, OpportunityContactRole> existingOpportunityContactRolesByContactId = new map<Id, OpportunityContactRole>();
		    	if(existingOpportunitiesContactRolesByContactId.containsKey(opportunity.Id)) {
		    		system.debug('Existing contact role found, assign to map');
		    		existingOpportunityContactRolesByContactId = existingOpportunitiesContactRolesByContactId.get(opportunity.Id);
		    	}
		    	else {
		    		system.debug('No contact role found, assign null');
		    		existingOpportunityContactRolesByContactId.put(opportunity.Id, null);
		    		system.debug('contact role assigned to null');
		    	}*/
		    	//end new code

		    	//initiate a new list to be used to compare existing contact roles to see if they're in the current account
		    	set<Id> contactIdsByAccount = new set<Id>();

				//For each contact related to the account for this current opportunity	    
				system.debug('1. before for loop check');		    	
		        for (Contact contact : accounts.get(opportunity.AccountId).Contacts) {
		        	//add the contact Id to the comparison list
		        	system.debug('2. for loop entered');
		        	contactIdsByAccount.add(contact.Id);
		        	system.debug('3. contact Id added');

		        	//pull the existing contact role for this contact if it exists, otherwise assign null
		        	system.debug('4. precheck to see if existing contact roles pulled correctly');
		        	//Original Code:
		        	OpportunityContactRole opportunityContactRole = existingOpportunityContactRolesByContactId.get(contact.Id);

		        	//new code
		        	/*system.debug('4.1 declare variable');
		        	OpportunityContactRole opportunityContactRole = new OpportunityContactRole();
		        	system.debug('4.2 variable declared, start if');
		        	if(existingOpportunityContactRolesByContactId.containsKey(contact.Id)) {
		        		system.debug('4.3 true');
		        		opportunityContactRole = existingOpportunityContactRolesByContactId.get(contact.Id);
		        	}
		        	else {
		        		opportunityContactRole = null;
		        		system.debug('4.4 false');
		        	}*/
		        	//end new code

		        	system.debug('5. existing contact role pulled');
		        	Id hasExistingOpportunityContactRole = opportunityContactRole == null ? null : opportunityContactRole.Id;
		        	system.debug('6. Id variable assigned');
		
					//updates master contact roles array for upsert
					system.debug('7. precheck on contact role added to list');
					system.debug('7.1 hasExistingOpportunityContactRole: ' + hasExistingOpportunityContactRole);
					system.debug('7.2 opportunityId: ' + opportunity.Id);
					system.debug('7.3 contactId: '  + contact.Id);
		            opportunityContactRoles.add( new OpportunityContactRole( Id = hasExistingOpportunityContactRole, OpportunityId = opportunity.Id, ContactId = contact.Id, Role = 'User'));	            
		            system.debug('8. contact role added to list.');
		        }
		        system.debug('end for loop');

		    	//compare existing contact roles to the contacts related to the account for this current opportunity. If not in the set, add to list to remove.
		    	system.debug('precheck to see if next line runs');
		    	for(OpportunityContactRole contactRole : existingOpportunityContactRolesByContactId.values()) {
		    		system.debug('checking to see if this was entered');
		    		system.debug('contact role' + contactRole);
		    		if(contactRole == null)
		    			continue;
		    		else if(!contactIdsByAccount.contains(contactRole.ContactId)) {
		        		deleteOpportunityContactRoles.add(contactRole);	
		        		system.debug('sub if entered, contact role added');
		    		}
		    	}
		    	system.debug('end for loop for removing contact roles');
		    }
		
			//Upsert contact roles
			system.debug('opportunity contact roles size list: ' + opportunityContactRoles.size());
		    if(!opportunityContactRoles.isEmpty()) {
		    	//List<OpportunityContactRole> rolesToInsert = new List<OpportunityContactRole>();
		    	//List<OpportunityContactRole> rolesToUpdate = new List<OpportunityContactRole>();
		    	//for(OpportunityContactRole contactRole : opportunityContactRoles) {
		    	//	if(contactRole.Id == null) 
		    	//		rolesToInsert.add(contactRole);
		    	//	else
		    	//		rolesToUpdate.add(contactRole);
		    	//}
		        upsert opportunityContactRoles;
		        //insert rolesToInsert;
		        //update rolesToUpdate;
		    }
		    system.debug('end upsert of opportunityContactRoles');

		    //Delete additional contact roles
		    system.debug('delete opportunity contat roles size: ' + deleteOpportunityContactRoles.size());
		    if(deleteOpportunityContactRoles != null)
		    	delete deleteOpportunityContactRoles;
		}
		TriggerHandler.firstRunOpportunityAddContactRoles = false;
	}
}


User-added image
jp1234jp1234
Can you add following statement in line 126 of OpportunityAddContactRoles class, just before you start a for-loop over the values of existingOpportunityContactRolesByContactId?
 
System.debug('What is in this map? ');
System.debug(existingOpportunityContactRolesByContactId);

 
Kelly KKelly K
Here's the new log:

User-added image
 
Kelly KKelly K
I should add, for that section that you're checking, since the opportunity is being inserted, there wouldn't be any existing contact roles, so a null map would be expected.
jp1234jp1234
No, actually source of that null object is exactly that map.  You are trying to looping through that map by calling values() method on that map.  But because it is null (and not empty map), you are getting null object error.  I think that insufficient access rights on cross-reference id is separate issue, though.
This was selected as the best answer
Kelly KKelly K
Interesting take - I guess when I use a query (instead of a map), if it's null it's more forgiving/intelligently handles a null query?

I added a null check to the front of the for loop and it appears to have fixed both errors.
if(existingOpportunityContactRolesByContactId != null) {
			    	for(OpportunityContactRole contactRole : existingOpportunityContactRolesByContactId.values()) {
			    		system.debug('checking to see if this was entered');
			    		system.debug('contact role' + contactRole);
			    		if(contactRole == null)
			    			continue;
			    		else if(!contactIdsByAccount.contains(contactRole.ContactId)) {
			        		deleteOpportunityContactRoles.add(contactRole);	
			        		system.debug('sub if entered, contact role added');
			    		}
			    	}
			    }


Also appears when I wrote the test class I didn't have it check for no contacts (thus causing a null map) either, so likely why I'm seeing it now.
 
Kelly KKelly K
And thank you for taking the time to look at this, I really appreciate it.
jp1234jp1234
You're welcome kkorynta.