Is there a specific scenario when this Service Agreement object needs to be created? For instance, this really sounds like a good candidate for a trigger. Say this object needs to be created at a certain stage of the opportunity or once certain information of that opportunity is created, a trigger would be perfect for this as you can add logic in the trigger to validate the conditions are met before actually creating anything. It also reduces a user from having to manually click a button. However I don't know exactly when this object is created and it could be arbitrary and require user input.
Take a look at the Trigger documentation. I would use something like:
trigger OpportunityTrigger on Opportunity (after update) {
List<Id> oppIds = new List<Id>();
for(Opportunity opp:Trigger.new){
oppIds.add(opp.Id);
}
List<Opportunity> oppsFromDb = [SELECT
Id, Stage, (SELECT Id FROM Service_Agreement__r)
FROM
Opportunity
WHERE
Id IN :oppIds
];
List<Service_Agreement__c> agreements = new List<Service_Agreement__c>();
for(Opportunity opp:oppsFromDb){
if(opp.Stage == 'Pending Service Agreement' && opp.Service_Agreement__r.size() == 0){
agreements.add(new Service_Agreement__c(OppId__c = opp.Id);
}
}
insert agreements;
}
Note: Objects in a trigger do not have their child object relationships loaded by default. That is why you need to loop over those objects and get them from the database again before performing an operation on them.
If you absolutely can not use a trigger, I would suggest using a custom button on the opportunity. This button should link to a custom Visualforce page. This Visualforce page should be blank except for an action on the apex:page element. Something like:
<apex:page controller="ServiceAgreementCreationController" action="{!doCreateAgreement}">
</apex:page>
The way this would work is you would have a ServiceAgreementCreationController class. You can't perform DML statements in your constructor, so you would set an action on the page to immediately perform that action. The constructor would look something like:
public class ServiceAgreementCreationController {
public Id oppId {get;set;}
public AllStarInvoiceGenerationController() {
oppId = System.currentPageReference().getParameters().get('oppId');
}
public PageReference doCreateAgreement(){
// Do all of your logic here. Load the opp, then create the Service agreement
Opportunity opp = [SELECT Id FROM Opportunity WHERE Id = :oppId];
Service_Agreement__c agreement = new Service_Agreement__c(OppId__c = opp.Id);
insert agreement;
// return the user back to the opportunity once it is done
PageReference reference = new ApexPages.StandardController(opp).view();
return reference;
}
}
This would eliminate the need for Javascript or creating anything custom on the Opportunity. You should be able to continue to the use the page layouts and move all of the functionality out into a separate page. The way this works would be:
- User clicks the "Create Service Agreement" button
- User is redirected to new page that loads as a blank white page briefly
- Immediately the action is called with no action taken by the user.
- Service agreement is created
- User is redirected to opportunity
This will all happen in a matter of a few seconds. Hopefully all of this helps. I would still suggest using a trigger first if possible.