After what you mentioned in the comments, it looks like updating the budget allocation causes an update to the opportunity, which is in turn in the middle of being updated by a trigger, which may cause the exception.
Perhaps you can work around this by adding future methods. Add these 2 methods to your class:
public with sharing class InsertBudgetOpp {
// existing code still goes here...
@future
public static void updateChildFuture (Set <Id> oppId, Date startDate, Date endDate, Integer totalBudget) {
Map < String, Budget_Allocation__c > baMap = updateChild (oppId, startDate, endDate, totalBudget);
upsert baMap.values();
}
@future
public static void insertChildFuture (Set <Id> oppId, Date startDate, Date endDate, Integer tb)
{
List <Budget_Allocation__c> baList = insertChild(Set <Id> oppId, Date startDate, Date endDate, Integer tb);
insert baList;
}
}
Then modify your trigger to call these future methods:
trigger EstimateBudgetPerMonth on Opportunity__c(after insert, before update) {
Date startDate = Date.newInstance(2008, 1, 1);
Date endDate = Date.newInstance(2008, 1, 30);
Set < Id > oppId = new Set < Id > ();
Integer totalBudget;
for (Opportunity__c opp: Trigger.new) {
startDate = opp.Campaign_Start_Date1__c;
endDate = opp.Campaign_End_Date1__c;
totalBudget = Integer.valueOf(opp.Budget1__c);
oppId.add(opp.Id);
}
if (Trigger.isInsert) {
System.debug('--===Insert==--#--===Insert==--#--===Insert==--');
InsertBudgetOpp.insertChildFuture(oppId, startDate, endDate, totalBudget);
} else if (Trigger.isUpdate) {
System.debug('--===Update==--#--===Update==--#--===Update==--');
InsertBudgetOpp.updateChildFuture(oppId, startDate, endDate, totalBudget);
}
}
What this will do is, when the trigger fires, start an asynchronous method (perhaps on a different thread) that will update the child budget allocation objects. They will complete sometime in the future (generally it takes less than a minute, but Salesforce will never guarantee a timeframe). This may or may not be adequate for your solution, but it might prevent the exception.
Also note that when you end a test (with Test.stopTest()), this will guarantee that all future methods are executed.
For more on future methods, read: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_invoking_future_methods.htm
The recursion problem is likely to be the result of the combination of this trigger and another trigger. (Your updated question now shows that and someone can answer about that.)
But the trigger you have posted has the problem that it deletes the Service__c and Accompaniment__c objects for all the Opportunity_car_set__c objects that the trigger is firing for and so is logically wrong for the bulk case (where there are multiple Opportunity_car_set__c objects). Doing the deletes inside the loop will also hit governor limits in the bulk case.
This approach (making use of relationship queries) ensures that it is the related Service__c and Accompaniment__c objects that are deleted:
trigger ModelVersionChange on Opportunity_car_set__c (before update) {
Service__c[] serviceDeletes = new Service__c[] {};
Accompaniment__c[] accompanimentDeletes = new Accompaniment__c[] {};
for (Opportunity_car_set__c ocs : [
select Model__c, Version__c,
(select Id from Services__r),
(select Id from Accompaniments__r)
from Opportunity_car_set__c
where Id in :Trigger.newMap.keySet()
]) {
Opportunity_car_set__c old = Trigger.oldMap.get(ocs.Id);
if (ocs.Model__c != old.Model__c || ocs.Version__c != old.Version__c) {
ocs.Price_HT__c = null;
ocs.TVN_Amount__c = null;
serviceDeletes.addAll(ocs.Services__r);
accompanimentDeletes.addAll(ocs.Accompaniments__r);
}
}
delete serviceDeletes;
delete accompanimentDeletes;
}
Best Answer
Convert it in a future method. You can only involve in record in a trigger in a single instance of an event. For example, an insert can recursively call an update, which can recursively call a delete. However, a convert is considered an update, so you can't call a convert in an update trigger. You'll need to convert it asynchronously, via a future method.