+ Start a Discussion
RatherGeekyRatherGeeky 

Trigger to Update Related Object from Custom Object (Offering $65 via paypal)

Here's the scenario: I have a custom object ('project') that is created via apex when an Opportunity is closed. I am trying to establish a 1:1 relationship (at least from the user's perspective), so I want the newly created project to be passed back to a lookup on the Opportunity.

Basically, I'm seeking to populate the Opportunity Project lookup with the id of the newly created project record so that they are linked both ways (instead of just linking one way, the project back to the opportunity).

I assume this can be done in an afterinsert trigger on the project object. But, I have been unable to update a related object via my trigger.

Something like this: thisOpp.Parent_Opportunity__r.Project_Profile__c = Project_Profile__c.Id;

I'm offering $65 via paypal to the first person that can provide me a working answer (this would include an apex trigger and the basic structure of a test class or an alternative solution).

Contact me with questions. (I will be working over the weekend.)

Message Edited by J Baze on 09-04-2009 05:21 PM
Best Answer chosen by Admin (Salesforce Developers) 
Guyver118Guyver118

try adding this to function closeOpp()

 

// Create a new project or contract
      if (won) {
        if ("{!Opportunity.Type}" == "Master Contract / IDIQ") {
          var contractId = createContract(thisOpp);
          window.location.href = "/" + contractId;
        } else {
          var projectId = createProject(thisOpp);

 

           // ****** assign projectid back to oppToClose.Project_Profile__c; 

           oppToClose.Project_Profile__c = projectId;

           //You have to update the opportunity again.

            try {
              res = sforce.connection.update([oppToClose]);
            } catch (err) {
               alert(err);
            }


          window.location.href = "/" + projectId;
        }       
      } else {
        window.location.href = "/{!Opportunity.Id}";
      }

 

All Answers

AmphroAmphro

Hey J Baze, 

It seems like all you need to do is create a lookup on both objects. Then you can handle the creation two ways: a controller or a trigger.

The trigger can create the Project and assign both lookups after the status of the Opportunity is closed. 

So something like this.

 

trigger createProject on Opportunity (before update) {
Map<Opportunity, Project__c> projects = new List<Opportunity, Project__c>();
Project_c project;
for (Opportunity opp : Trigger.new()) {
if (opp.Status = 'closed') {
project = new Project__c();
project.Opportunity = opp;
projects.add(opp, project);
}
}
insert projects.valueSet();
for (Opportunity opp : projects.keySet()) {
opp.Projecy__c = projects.get(opp);
}

That should do it. I typed it off the top of my head so it won't directly work but the concept should work. Unless I misunderstood what your trying to do. I made it to handle batches as well. You might have to insert a check to make sure that a project isn't already created, encase the Opportunity is set to closed, opened again, then closed.

I can create a test for you too if this sounds like what you want. 

RatherGeekyRatherGeeky

Amphro:

Thanks for your response.

I already have a visualforce page and a button that creates the project record (using javascript)

 

You said that: "The trigger can create the Project and assign both lookups after the status of the Opportunity is closed."

 

Is this possible to do in the following page? (I know that this is a lot to process, but I'm thinking that the code would go where i have put:

// ****** assign projectid back to oppToClose.Project_Profile__c;  

 I have been unsuccessful with this method. I tried using oppToClose.Project_Profile__c = projectId;

<apex:page standardController="Opportunity" title="Close Opportunity">

<apex:includeScript value="/soap/ajax/12.0/connection.js"/>
<apex:includeScript value="/soap/ajax/12.0/apex.js"/>


<script type="text/javascript">

/*
** onClickClose
**
** Click handler for "Close Opportunity" button
*/
function onClickClose() {

// Close the opportunity
var status = document.getElementById("{!$Component.f.status}");
status.innerHTML = "Closing opportunity. Please wait...";
document.body.style.cursor = "wait";
setTimeout("closeOpp();", 100);
}


/*
** toggleReason
**
** Toggle UI elements depending on whether the
** Oppportunity is won or lost
*/
function toggleReason() {

// Inspect DOM for UI elements
var wonElement = document.getElementById("{!$Component.f.pb.pbs.si2.isWon}");
var reasonElement = document.getElementById("{!$Component.f.pb.pbs.si3.closeReason}");
var isWon = (wonElement.value == "Yes");

// Remove existing items from reason list
while (reasonElement.options.length > 0) {
reasonElement.remove(0);
}

// Add reasons
if (isWon) {
addReason(reasonElement, "Selected");
addReason(reasonElement, "SOW Signed");
} else {
addReason(reasonElement, "Lost - Competitor");
addReason(reasonElement, "Lost - No Decision");
addReason(reasonElement, "Lost - Insufficient Budget");
addReason(reasonElement, "Lost - Land Survey Selected Instead");
addReason(reasonElement, "Lost - Client Not Selected");
addReason(reasonElement, "Lost - Reason Unknown");
addReason(reasonElement, "Lost - Cancelled");
addReason(reasonElement, "Lost - Change in Scope");
addReason(reasonElement, "Unqualified");
}
reasonElement.selectedIndex = 0;

}


/*
** addReason
**
** Dynamically add items to the reason won/lost
** select list
*/
function addReason(list, reason) {
var opt = document.createElement("option");
opt.text = reason;
opt.value = reason;

if(document.all && !window.opera) {
list.add(opt);
} else {
list.add(opt, null);
}
}


/*
** closeOpp
**
** Closes opportunities and creates
** follow-on projects and opportunities as appropriate
**

function closeOpp() {

// Set session header
sforce.connection.sessionId = '{!$Api.Session_ID}';

// Get DOM elements
var wonElement = document.getElementById("{!$Component.f.pb.pbs.si2.isWon}");
var reasonElement = document.getElementById("{!$Component.f.pb.pbs.si3.closeReason}");
var won = (wonElement.value == "Yes");

// Close the current opportunity

var oppToClose = new sforce.SObject("Opportunity");
oppToClose.Id = "{!Opportunity.Id}";
oppToClose.StageName = reasonElement.value;

try {
res = sforce.connection.update([oppToClose]);
} catch (err) {
alert(err);
}

// Retrieve this opp from the database
var thisOpp = retrieveOpp();


// Create recurring Opportunity
if("true" == "{!Opportunity.Recurring__c}") {
createRecurrence(thisOpp);
}

// Create a new project or contract
if (won) {
if ("{!Opportunity.Type}" == "Master Contract / IDIQ") {
var contractId = createContract(thisOpp);
window.location.href = "/" + contractId;
} else {
var projectId = createProject(thisOpp);

 

  // ****** assign projectid back to oppToClose.Project_Profile__c;


window.location.href = "/" + projectId;
}
} else {
window.location.href = "/{!Opportunity.Id}";
}
}


/*
** retrieveOpp
**
** Retrieves the current opportunity as an SObject that can be copied in memory
*/
function retrieveOpp() {
var fields = "";

// Generate list of fields we expect to copy to either an opportunity or project
var dResult = sforce.connection.describeSObject("Opportunity");
for (var i=0; i < dResult.fields.length; i++) {
var f = dResult.fields[i];
fields += (i > 0 ? "," : "") + f.name;
}

// Retrieve the Opportunity
opp = sforce.connection.retrieve(fields, "Opportunity", ["{!Opportunity.Id}"]);
return opp[0];
}


/*
** createRecurrence()
**
** Creates a new recurring opportunity and links it
** to a newly closed opportunity

*/
function createRecurrence(sourceOpp) {
var ret = "";
var res = "";

// Calcuate close date for next occurence
var recurDate = sourceOpp.getDate("CloseDate");
var intervalMonths = sourceOpp.getInt("Recur_Interval_Months__c");
if (intervalMonths) {
recurDate.setMonth(recurDate.getMonth() + intervalMonths);
}

// Initialize new opportunity from closed opportunity
var opp = new sforce.SObject("Opportunity");
opp.AccountId = sourceOpp.AccountId;
opp.Name = sourceOpp.Name;
opp.Amount = sourceOpp.Amount;
opp.StageName = "Needs Analysis"; // TODO -- confirm stage
opp.CloseDate = recurDate;
opp.Parent_Opportunity__c = sourceOpp.Id;
opp.Contract__c = sourceOpp.Contract__c;
opp.Recurring__c = sourceOpp.Recurring__c;
opp.Recur_Interval_Months__c = sourceOpp.Recur_Interval_Months;
opp.Created_From_Recurring__c = true;
opp.Type = sourceOpp.Type;
opp.Secondary_Account__c = sourceOpp.Secondary_Account__c;
opp.Description = sourceOpp.Description;
opp.NextStep = "Initiate Contact";
opp.Competitive_Deal__c = sourceOpp.Competitive_Deal__c;
opp.Main_Competitor__c = sourceOpp.Main_Competitor__c;
opp.City__c = sourceOpp.City__c;
opp.County__c = sourceOpp.County__c;
opp.State_List__c = sourceOpp.State_List__c;
opp.Country__c = sourceOpp.Country__c;
opp.Section__c = sourceOpp.Section__c;
opp.Township__c = sourceOpp.Township__c;
opp.Range__c = sourceOpp.Range__c;
opp.Location_Details__c = sourceOpp.Location_Details__c;
opp.Longitude__c = sourceOpp.Longitude__c;
opp.Latitude__c = sourceOpp.Latitude__c;
opp.LeadSource = "Repeat Work";
opp.Client_Relationship__c = "Current Client - Excellent";
opp.Evaluation_Process__c = sourceOpp.Evaluation_Process__c;
opp.Suitable_Project_Location__c = sourceOpp.Suitable_Project_Location__c;
opp.Potential_Follow_On__c = sourceOpp.Potential_Follow_On__c;
opp.Primary_Mapping_Solution_Filtered__c = sourceOpp.PPrimary_Mapping_Solution_Filtered__c;
opp.Primary_Mapping_Solution_Category__c = sourceOpp.Primary_Mapping_Solution_Category__c
opp.Mapping_Services__c = sourceOpp.Mapping_Services__c;
opp.Size_Units__c = sourceOpp.Size_Units__c;
opp.Size__c = sourceOpp.Size__c;
opp.Deliverables__c = sourceOpp.Deliverables__c;
opp.Acquisition_Specifications__c = sourceOpp.Acquisition_Specifications__c;
opp.Mapping_Specifications__c = sourceOpp.Mapping_Specifications__c;
opp.Billing_Options__c = sourceOpp.Billing_Options__c;
opp.Recur_Interval_Months__c = sourceOpp.Recur_Interval_Months__c;
opp.Owner_Region__c = sourceOpp.Owner_Region__c;


// TODO -- map additional fields as needed

// Create the Opportunity
try {
res = sforce.connection.create([opp]);
} catch (err) {
alert(err);
}
if (res[0].getBoolean("success")) {
ret = res[0].id;
}

return (ret);
}


/*
** createContract()
** Creates a new contract for a closed opportunity
**
** This logic is implemented in Javascript instead of an APEX controller
** extension to avoid the need for Aero-Metric to maintain
** and deploy APEX code.
*/
function createContract(sourceOpp) {
var ret = "";
var res = "";

// Initialize contract from Opportunity data
var contract = new sforce.SObject("Contract");
contract.AccountId = sourceOpp.AccountId;
contract.Name = sourceOpp.Name;
contract.Parent_Opportunity__c = sourceOpp.Id;
contract.Primary_Mapping_Solution__c = sourceOpp.Primary_Mapping_Solution__c;
contract.Contract_Amount__c = sourceOpp.Estimated_Contract_Amount__c;
contract.Secondary_Account__c = sourceOpp.Secondary_Account__c;
contract.Parent_Master_Contract__c = sourceOpp.Contract__c;
contract.Primary_Mapping_Solution_Category__c = sourceOpp.Primary_Mapping_Solution_Category__c;
contract.Primary_Mapping_Solution_Filtered__c = sourceOpp.Primary_Mapping_Solution_Filtered__c;


// TODO -- map additional fields as needed

// Create the contract
try {
res = sforce.connection.create([contract]);
} catch (err) {
alert(err);
}
if (res[0].getBoolean("success")) {
ret = res[0].id;
}

return (ret);
}


/*
** createProject()
**
** Creates a new project and links it to a newly
** closed oppportunity
**
** This logic is implemented in Javascript instead of an APEX controller
** extension to avoid the need for Aero-Metric to maintain
** and deploy APEX code.
*/
function createProject(sourceOpp) {
var ret = "";
var res = "";

// Initialize project from Opportunity data
var project = new sforce.SObject("Summary__c");
project.Name = "-";
project.projName__c = sourceOpp.Name;
project.projAccount__c = sourceOpp.AccountId;
project.Secondary_Account__c = sourceOpp.Secondary_Account__c;
project.Opportunity__c = sourceOpp.Id;
project.Contract__c = sourceOpp.Contract__c;
project.Parent_Project__c = sourceOpp.Parent_Project__c;
project.State_List__c = sourceOpp.State_List__c;
project.City__c = sourceOpp.City__c;
project.County__c = sourceOpp.County__c;
project.Country__c = sourceOpp.Country__c;
project.Section__c = sourceOpp.Section__c;
project.Township__c = sourceOpp.Township__c;
project.Range__c = sourceOpp.Range__c;
project.Latitude__c = sourceOpp.Latitude__c;
project.Longitude__c = sourceOpp.Longitude__c;
project.Location_Details__c = sourceOpp.Location_Details__c;
project.Primary_Mapping_Solution__c = sourceOpp.Primary_Mapping_Solution__c;
project.Size__c = sourceOpp.Size__c;
project.Size_Units__c = sourceOpp.Size_Units__c;
project.Booked_Date__c = sourceOpp.CloseDate;
project.Amount__c = sourceOpp.Amount;
project.Start_Date__c = sourceOpp.Estimated_Project_Start_Date__c;
project.Complete_Date__c = sourceOpp.Estimated_Project_Completion_Date__c;
project.Mapping_Specifications__c = sourceOpp.Mapping_Specifications__c;
project.Billing_Options__c = sourceOpp.Billing_Options__c;
project.Acquisition_Specifications__c = sourceOpp.Acquisition_Specifications__c;
project.Deliverables__c = sourceOpp.Deliverables__c;
project.Mapping_Services__c = sourceOpp.Mapping_Services__c;
project.OwnerId = sourceOpp.OwnerId;
project.Owner_Region__c = sourceOpp.Owner_Region__c;
project.Primary_Mapping_Solution_Filtered__c = sourceOpp.Primary_Mapping_Solution_Filtered__c;
project.Primary_Mapping_Solution_Category__c = sourceOpp.Primary_Mapping_Solution_Category__c;
project.Description__c = sourceOpp.Description;

// TODO -- map additional fields as needed

// Create the project
try {
res = sforce.connection.create([project]);
} catch (err) {
alert(err);
}
if (res[0].getBoolean("success")) {
ret = res[0].id;
}

return (ret);
}


</script>



<apex:sectionHeader title="Close Opportunity" subtitle="{!Opportunity.Name} - Resulting in a {!Opportunity.Type}"/>

<apex:form id="f">
<apex:pageBlock title="Opportunity Win/Loss Information" id="pb">
<apex:pageBlockSection columns="1" title="Why is this Opportunity Closing?" id="pbs">

<apex:outputField value="{!Opportunity.Account.Name}"/>
<apex:outputField value="{!Opportunity.Region_Client_ID__c}"/>

<apex:pageBlockSectionItem id="si1">
<apex:outputLabel value="Current Sales Stage"/>
<apex:outputText value="{!Opportunity.StageName}"/>
</apex:pageBlockSectionItem>

<apex:pageBlockSectionItem id="si2">
<apex:outputLabel value="Did we win this deal?"/>
<apex:selectList id="isWon" size="1" onChange="toggleReason()">
<apex:selectOption itemLabel="Yes" itemValue="Yes"/>
<apex:selectOption itemLabel="No" itemValue="No"/>
</apex:selectList>
</apex:pageBlockSectionItem>

<apex:pageBlockSectionItem id="si3">
<apex:outputLabel value="What was the reason?"/>
<apex:selectList id="closeReason" size="1">
<apex:selectOption itemLabel="Selected" itemValue="Selected"/>
<apex:selectOption itemLabel="SOW Signed" itemValue="SOW Signed"/>
</apex:selectList>
</apex:pageBlockSectionItem>

</apex:pageBlockSection>
</apex:pageBlock>

<input class="btn" type="button" title="Close Opportunity" name="close" value=" Close Opportunity " onClick="onClickClose()"/>
<apex:commandButton action="{!cancel}" value="Cancel" id="cancelButton"/>
<apex:outputText value="" id="status"/>

</apex:form>

</apex:page>

 

Message Edited by J Baze on 09-05-2009 02:48 PM
Message Edited by J Baze on 09-05-2009 02:49 PM
Message Edited by J Baze on 09-05-2009 02:50 PM
AmphroAmphro

Just out of curiosity, is there any reason why you aren't using an extension? Most of that would be more natural, and probably a lot shorter, if it was in an apex class. Plus, you can add test and debug in a class. 

However it does seem like that should work. Can you describe what you mean by "unsuccessful?" Does it produce an error?

You might also try  oppToClose.Project_Profile__c.id = projectId;   Although I don't think that will work. 

RatherGeekyRatherGeeky

Two reasons we aren't using an extension:

1. A consultant helped us set it up when we initially rolled out.
2. I am just starting with apex and haven't learned enough yet to be confident to redevelop this page in an extension.

If it's possible to add this to our existing page then I won't need the test class mentioned in my initial post. That was just my initial thoughts if it involved a trigger. 

Regarding "unsuccessful" code: it freezes and does not continue with processing. 

Tried oppToClose.Project_Profile__c.id = projectId;.... did not work.

Any other suggestions would be much appreciated!

Message Edited by J Baze on 09-05-2009 07:59 PM
AmphroAmphro
Since your not confident moving the whole thing over, I would create a test page and apex class with that piece of code. Apex will give you an actual error message which will help you debug.
Guyver118Guyver118

This should really be easy to do, my understanding is that you want to populate the project id back to the opportunity that created it?

 

Is there a trigger that creates the project when opportunity is closed if so just put a project lookup on the opportunity and update this from the trigger that created it?

 

Opportunity Trigger

 

{

           for(opportunity a : trigger.new)

           {

                    if(a.StageName.equals('Closed'))

                    {

                              //Create project

                              //and assign this opp with project id

                              a.Project__c = pb.Id;

                     }

                     

           }

}

Guyver118Guyver118

try adding this to function closeOpp()

 

// Create a new project or contract
      if (won) {
        if ("{!Opportunity.Type}" == "Master Contract / IDIQ") {
          var contractId = createContract(thisOpp);
          window.location.href = "/" + contractId;
        } else {
          var projectId = createProject(thisOpp);

 

           // ****** assign projectid back to oppToClose.Project_Profile__c; 

           oppToClose.Project_Profile__c = projectId;

           //You have to update the opportunity again.

            try {
              res = sforce.connection.update([oppToClose]);
            } catch (err) {
               alert(err);
            }


          window.location.href = "/" + projectId;
        }       
      } else {
        window.location.href = "/{!Opportunity.Id}";
      }

 

This was selected as the best answer
RatherGeekyRatherGeeky
Guyver118: That worked! You win. Please send me a private msg with your paypal username (unless you just provided an answer for kicks).
Message Edited by J Baze on 09-08-2009 01:25 AM
RatherGeekyRatherGeeky
Amphro: Appreciate your feedback. Thanks for posting.
Guyver118Guyver118
Its cool, i really like this community and hope we can all help each other out :0
RatherGeekyRatherGeeky
Really? Well, cool! Thanks for helping me out in my time of desperation. :) I really, really appreciate it. (I was implementing several projects over the holiday weekend and got this one working just in time.)