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
Maimoona S.Maimoona S. 

Opportunity & Opportunity Line Item to Service Contract - Best Practice

Hi there,

I have a requirement to create a service Contract when Opportunity is Cclosed won and copy all Opp line items to contract Line Items.

Here is the sample Code , there are two DMLs in the loop , one for ServiceContract and other for contractlineitem(that need service contract id). 

Map<Id,List<OpportunityLineItem>> OppLineItems = new Map<Id,List<OpportunityLineItem>>();

for(OpportunityLineItem ol : [Select opportunityId,UnitPrice, Quantity , Description From OpportunityLineItem where 
opportunityId IN: Trigger.new])
    {
    	List<OpportunityLineItem> tempList = new List<OpportunityLineItem>();
    	
    	if(OppLineItems.containskey(ol.opportunityId))
    	{
    		templist = OppLineItems.get(ol.OpportunityId) ; 	
    		templist.add(ol);
    		
    		OppLineItems.put(ol.OpportunityId,tempList);
    	}
    	else
    	{ 	
    		templist.add(ol);
    		
    		OppLineItems.put(ol.OpportunityId,tempList);		
    	}
}

//Create Service Contract and Contract Line Item

for(Opportunity o : Trigger.New)
        {
        	if(o.IsClosed && o.Iswon)
        	{
				 ServiceContract sc = new ServiceContract();
				 
				 sc.AccountId = o.AccountId;
				 sc.Name = o.Name;
				 sc.ApprovalStatus = 'Draft';
				 sc.StartDate = System.today();
				 insert sc;

                                 List<ContractLineItem> ContractLineInsert = new List<ContractLineItem>();
                                for(OpportunityLineItem ol : OppLineItems.get(o.Id)
                                {
                                             ContractLineItem cl = new ContractLineItem();
                                             cl.ServiceContractId = sc.Id;
                                             // some other field mapping from opp line item
                                             ContractLineInsert.add(cl);
                                 }
                                 //insert line item
                                 insert ContractLineInsert;
    
    	
        	}
        }

I have a workaround to avoid dml in loop is that insert service contract on Opportunity trigger , set some custom field that hold opportunity id on service contract.
And on ServiceContract add another trigger that will query opportunity line item relevant to opportunity (using custom field on servicecontract object that hold opportunity id) and insert them as contract line item. 

I want to know if there is any other way of avoiding DMLs in loop as this could cause DML limit.
Best Answer chosen by Maimoona S.
AshlekhAshlekh
Hi,

Below code is customize and follow best practise 

//By this you will get oppty wiht line items and don't need for loop
Map<Id,Opportunity> MapofOpptywithLineItems = new Map<id,opportunity>([select id ,other fields (select id ,other fields from opportunitylineitems) from opportunity where id in :Trigger,newMap.KeySet()]);

Map<Id,ServiceContract> MapOfServiceContract = new Map<id,ServiceContract>();
for(Opportunity o : MapofOpptywithLineItems.values())
{
	if(o.IsClosed && o.Iswon)
	{
		ServiceContract sc = new ServiceContract();
		sc.AccountId = o.AccountId;
		sc.Name = o.Name;
		sc.ApprovalStatus = 'Draft';
		sc.StartDate = System.today();
		MapOfServiceContract.put(o.id,sc);
	}
}
if(MapOfServiceContract.size()>0)
{
	insert MapOfServiceContract.values();
}
List<ContractLineItem> ContractLineInsert = new List<ContractLineItem>();
for(Opportunity o : MapofOpptywithLineItems.values())
{
	String scId;
	if(MapOfServiceContract.containsKey(o.id))
		scId = MapOfServiceContract.get(o.id).id;
	if(scId != null)
	{	
		for(OpportunityLineItem li :o.opportunitylineItems)
		{
			ContractLineItem cl = new ContractLineItem();
			cl.ServiceContractId = sc.Id;
			ContractLineInsert.add(cl);
		}
	}
}
if(ContractLineInsert.size()>0)
{
	insert ContractLineInsert;
}


All Answers

AshlekhAshlekh
Hi,

Below code is customize and follow best practise 

//By this you will get oppty wiht line items and don't need for loop
Map<Id,Opportunity> MapofOpptywithLineItems = new Map<id,opportunity>([select id ,other fields (select id ,other fields from opportunitylineitems) from opportunity where id in :Trigger,newMap.KeySet()]);

Map<Id,ServiceContract> MapOfServiceContract = new Map<id,ServiceContract>();
for(Opportunity o : MapofOpptywithLineItems.values())
{
	if(o.IsClosed && o.Iswon)
	{
		ServiceContract sc = new ServiceContract();
		sc.AccountId = o.AccountId;
		sc.Name = o.Name;
		sc.ApprovalStatus = 'Draft';
		sc.StartDate = System.today();
		MapOfServiceContract.put(o.id,sc);
	}
}
if(MapOfServiceContract.size()>0)
{
	insert MapOfServiceContract.values();
}
List<ContractLineItem> ContractLineInsert = new List<ContractLineItem>();
for(Opportunity o : MapofOpptywithLineItems.values())
{
	String scId;
	if(MapOfServiceContract.containsKey(o.id))
		scId = MapOfServiceContract.get(o.id).id;
	if(scId != null)
	{	
		for(OpportunityLineItem li :o.opportunitylineItems)
		{
			ContractLineItem cl = new ContractLineItem();
			cl.ServiceContractId = sc.Id;
			ContractLineInsert.add(cl);
		}
	}
}
if(ContractLineInsert.size()>0)
{
	insert ContractLineInsert;
}


This was selected as the best answer
Maimoona S.Maimoona S.
Thank you so much for your response McGraw .. this helps alot ...