1

I have an object of Opportunity and each Opportunity has a related list of Quotes. There is a checkbox on the Opportunity to 'Create Project from Opportunity' that will cause the trigger to run and create a Project. On the list of related Quotes there will be ONE that is marked as Primary. When the trigger runs it will pull several fields from the Opportunity, but also needs to pull fields from the Primary Quote to populate the new record. I'm struggling to access the field information from the related Quote - I have tried several different things, from mapping to looping, but to no avial. Below is the code (although several mapping lines removed for readability, none of which are causing issues.) I have added where I need to pull in the QUOTE field on the last line of the mapping.

Is someone able to help me out?

trigger ProjectCreationOpportunity on Opportunity (before insert, before update)  {
List<pse__Proj__c> projectList = new List<pse__Proj__c>();



FOR(Opportunity opp : Trigger.New){
IF(Trigger.isInsert){
    IF(opp.Create_Project__c = true){
    projectList.add(new pse__Proj__c(Name = opp.ShortOppName__c+ ' ' + opp.Account_Number__c
    ,pse__Account__c = opp.AccountId
    ,pse__Is_Active__c = true
    ,pse__Is_Billable__c = true));
    }
    }

IF(Trigger.isUpdate){
Opportunity oldopp = Trigger.oldMap.get(opp.id);
IF(opp.Create_Project__c == true && oldopp.Create_Project__c == false){
    projectList.add(new  pse__Proj__c(
    Name = opp.ShortOppName__c + ' ' + opp.Account_Number__c
    ,pse__Account__c = opp.AccountId
    ,pse__Is_Active__c = true
    ,pse__Is_Billable__c = true
    ,Order_Type__c = NEED VALUE FROM Quote__r HERE.Order_Type__c));
    }
    }
    }
    IF(projectList.size()>0){
    Insert projectList;
    }
 }
1

Trigger context variables (trigger.new, trigger.oldMap, etc...) only contain data for the object your trigger is defined on. If you want to get data on related objects (parent data like Opportunity.Account.Name or child data, like Opportunity.OpportunityLineItems[0].UnitPrice), you need to explicitly query for it.

The rule of thumb is that if you need more than one dot "." to access the data, you need to query for it.

There is more than one way to query for related data, but probably the easiest way to go about this when you're working with child record data is to use a parent-child subquery. Something like...

// Using a Map here so that you can get at your target data using the Opportunity Id
//   from the trigger context variable
Map<Id, Opportunity> oppsWithPrimaryQuote = new Map<Id, Opportunity>([
    SELECT Id, (SELECT Id, Order_Type__c FROM Quotes WHERE isPrimary = true)
    FROM Opportunity
    WHERE Id IN :trigger.newMap.keySet()]);

I'm assuming that you're using the standard Quote object and the standard relationship between Opportunity and Quote.

That extra (SELECT ... FROM Quotes WHERE ...) is the parent-child subquery. The benefit of this approach is that you don't need to do any of the work to tie the child records back to the parent records (the SOQL takes care of it for us here).

Child records queried like that end up in a List<SObject> embedded within each result of the parent record. I don't think you'll need to worry about the Aggregate query has too many rows for direct assignment, use FOR loop error (which can happen if there are too many child records), so accessing that data should be as simple as

system.debug(oppsWithPrimaryQuote.get(myOppId).Quotes[0].Order_Type__c);
| improve this answer | |
  • Derek - I beat my head against the wall yesterday going through this and you solved it in 7 minutes (from when I posted!) THANK YOU For clarifying. I'm going to post my full code below in case it's beneficial to someone else. – Elizabeth Shaw Apr 28 at 13:57
0
trigger ProjectCreationOpportunity on Opportunity (before insert, before update)  {
List<pse__Proj__c> projectList = new List<pse__Proj__c>();



FOR(Opportunity opp : Trigger.New){

Map<Id, Opportunity> oppsWithPrimaryQuote = new Map<Id, Opportunity>([
    SELECT Id, (SELECT Id, Order_Type__c, Payment_Terms__c FROM Quote__r LIMIT 1)
    FROM Opportunity
    WHERE Id IN :trigger.newMap.keySet()]);


IF(Trigger.isInsert){
    IF(opp.Create_Project__c = true){
    projectList.add(new pse__Proj__c(Name = opp.ShortOppName__c));
    }
    }

IF(Trigger.isUpdate){
Opportunity oldopp = Trigger.oldMap.get(opp.id);
IF(opp.Create_Project__c == true && oldopp.Create_Project__c == false){
    projectList.add(new  pse__Proj__c(
    Name = opp.ShortOppName__c + ' ' + opp.Account_Number__c
    ,pse__Account__c = opp.AccountId
    ,pse__Is_Active__c = true
    ,pse__Is_Billable__c = true
    ,CurrencyIsoCode = opp.CurrencyIsoCode
    ,pse__End_Date__c = opp.PSA_Project_End_Date__c
    ,Internal_Notes__c = opp.Comments__c
    ,pse__Opportunity__c = opp.Id
    ,pse__Opportunity_Owner__c = opp.OwnerId
    ,pse__Practice__c = 'a20360000016A7d'
    ,Primary_Sales_Engineer__c = opp.Primary_Sales_Engineer__c
    ,pse__Project_Status__c = 'Green'
    ,pse__Project_Type__c = 'Customer Project SRC'
    ,Sales_Engineers__c = opp.Sales_Engineers__c
    ,SRC_Solutioning_Notes__c = opp.SRC_Solutioning_Notes__c
    ,pse__Stage__c = 'Provisional'
    ,pse__Start_Date__c = opp.CloseDate
    ,Order_Type__c = oppsWithPrimaryQuote.get(opp.Id).Quote__r[0].Order_Type__c
    ,Payment_Terms__c = oppsWithPrimaryQuote.get(opp.Id).Quote__r[0].Payment_Terms__c
    ,Quote__c = oppsWithPrimaryQuote.get(opp.Id).Quote__r[0].Id));
    }
    }
    }
    IF(projectList.size()>0){
    Insert projectList;
    }
 }
| improve this answer | |
  • 1. I have not completed the mapping for Insert because the details are still being fleshed out, but the answer to the mapping for an update are correct. 2. I was not using the Quote object out of the box, but instead a custom Quote object, hence the reference to the '__r' 3. I limited my query to 1 for the time being, as the 'Is Primary Quote' field is currently under discussion. – Elizabeth Shaw Apr 28 at 13:59
  • FYI, you have a bug in your code: IF(opp.Create_Project__c = true){; this is an assignment, so it will check the box and always enter the true branch for this if statement. I generally advise that you do not compare against true or false for Boolean values, because they are already Boolean values (e.g. IF(opp.Create_Project__c) {); this habit prevents this very subtle error from getting in your code. – sfdcfox Apr 28 at 14:54
  • It also looks like you put the query for related data inside your for loop. You'll want to make sure that all queries (and dml too, though that doesn't appear to be an issue here) are outside of all loops. – Derek F Apr 28 at 15:09
  • Derek - It's interesting that you point that out about the checkbox, because I had that issue when I had a comparison of current != previous - it kept just resetting it and firing the trigger. The look for the previous mapping to be false seems to have alleviated it, but it sounds like I should review it again. I played around with this mapping quite a bit before your help and for some reason kept getting errors about the objects. I just moved it and don't have any errors. Thank you so much and thank you for being on the lookout! – Elizabeth Shaw Apr 28 at 17:00
  • @ElizabethShaw I wasn't the one to point out the checkbox thing, that was another user (sfdcfox) – Derek F Apr 28 at 17:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.