You need to sign in to do that
Don't have an account?
Trigger to copy opportunity product name to account custom field
trigger productname on Account(after insert,after update)
{
List<Account> lstAccounts = [Select ProductNameCopy__c from Account ];
List<Opportunity> lstopptys = [SELECT Id, Name FROM Opportunity];
List<Id> oppIds = new List<Id>();
List<OpportunityLineItem> lstopptylineitem = [SELECT Id, PricebookEntry.Product2.Name FROM OpportunityLineItem WHERE OpportunityId IN :oppIds];
for(Opportunity o : lstopptys)
{
oppIds.add(o.Id);
}
for(Account objAccount : lstAccounts)
{
for(Opportunity objoppty : lstopptys)
{
for(opportunitylineitem objopplineitem : lstopptylineitem)
{
objAccount.ProductNameCopy__c=objopplineitem.PricebookEntry.Product2.Name;
update objAccount;
}
}
}
}
I am using the above trigger to copy the opportunity product names in a custom field in the account object.
This trigger is saved without error.
But doesn't copy the product name in the Account's custom field(ProductNameCopy).
Please advise!!!
trigger MyAccountTrigger on Account(before update, before insert) {
Map<ID,Set<String>> aIdToUniqueProductSetMap = new Map<ID,Set<String>> ();
// Go through each Oppo and collect a unique set of Product names from all OLI
for (Opportunity o : [select id, accountId,
(select id, PricebookEntry.Product2.Name from OpportunityLineItems)
from Opportunity where accountId IN :Trigger.new] ) {
Set<String> productsInOppoSet = new Set<String> (); // reset our set for this Oppo
for (OpportunityLineItem oli : o.opportunityLineItems)
productsInOppoSet.add(oli.PricebookEntry.Product2.Name);
// With the unique set, add it into our Map associating each account to its unique product set
if (aIdToUniqueProductSetMap.containsKey(o.accountId)) {
Set<String> uniqueProdSet = aIdToUniqueProductSetMap.get(o.accountId); // add this Oppo's unique set to whatever we have so far
uniqueProdSet.addAll(productsInOppoSet);
aIdToUniqueProductSetMap.put(o.accountId,uniqueProdSet); // put it back in the Map
}
else
aIdToUniqueProductSetMap.put(o.accountId,productsInOppoSet); // first Oppo for this Account; simply do a put
}
// All done with Oppos, now put back into each triggered Account
for (Account a: Trigger.new) {
if (aIdToUniqueProductSetMap.containsKey(a.id)) { // only for those Accounts that had Oppos with products
String concatenatedProducts = '';
for (String p : aIdToUniqueProductSetMap.get(a.id))
concatenatedProducts = concatenatedProducts + (concatenatedProducts.length() == 0 ? '' : ' ') + p; // separate with spaces; other delims possible
a.description = concatenatedProducts ; // when the trigger completes, the triggered Accounts get updated
}
}
}
All Answers
sfdclearn:
First of all, use your old friend System.debug(..) to understand what is going on in your code.
This trigger has a few conceptual issues
1. lstAccounts is retrieving all Accounts in the database; you need to use Trigger.new to get the list of Accounts in the trigger list
2. lstOpptys should be a list of only the Opportunities that are children of the triggered Accounts
3 - There can be many Opportunities for a given Account and they cna have many products. How can you only place one product on the Account?
4 - The trigger will only fire if the Account is updated, it will not fire when you add an Opportunity product or modify an Opportunity
Check out the APEX documentation on Triggers, especially the examples to understand how to write triggers.
Hi,
Thanks for your response! I want this trigger to trigger only When the Account status is changed(though i haven't added that condition in the trigger, for now its in the Account insert/update).When the Account status is changed i want all the product
name(opp products) associated to the account to be copied in the Custom text field in the account.
We have a workflow rule that sends an email out when Account status is changed.If we have product name copied on the custom text field we can merge the field in the email template so email template specifically says the product names associated to the account.
Also I have made modifications in trigger.
trigger productname on Account(after insert,after update)
{
List<Account> lstAccounts = [Select ProductNameCopy__c from Account];
List<Id> AccIds =new List<id>();
for(Account a:lstAccounts)
{
AccIds.add(a.Id);
}
system.debug(1);
List<Opportunity> lstopptys = [SELECT Id, Name FROM Opportunity WHERE AccountId IN : AccIds];
List<Id> oppIds = new List<Id>();
for(Opportunity o : lstopptys)
{
oppIds.add(o.Id);
}
system.debug(2);
List<OpportunityLineItem> lstopptylineitem = [SELECT Id, PricebookEntry.Product2.Name FROM OpportunityLineItem WHERE OpportunityId IN :oppIds];
for(Account objAccount : trigger.new)
{
for(Opportunity objoppty : lstopptys)
{
for(opportunitylineitem objopplineitem : lstopptylineitem)
{
if(objAccount.AccountStatus__c=='New')
{
objAccount.ProductNameCopy__c=objAccount.productNameCopy__c + objopplineitem.PricebookEntry.Product2.Name;
system.debug(3);
}
}
}
update objAccount;
}
}
Error: Invalid Data.
Review all error messages below to correct your data.
Apex trigger productname caused an unexpected exception, contact your administrator: productname: execution of AfterUpdate caused by: System.FinalException: Record is read-only: Trigger.productname: line 29, column 1
But it throws when i save an account
You are trying to work on Trigger.New Object which is read only in after insert/update. you need to Query the database and get the results and then work on it .
Updating Trigger.New Object in After update/insert is not allowed.
This is the spolier----
for(Account objAccount : trigger.new)
objAccount.ProductNameCopy__c=objAccount.productNameCopy__c + objopplineitem.PricebookEntry.Product2.Name;
My personal preference is to do updates on fields in SObject X within the before trigger on SObject X rather than after triggers. Use after triggers to do updates on related records. So...ignoring whether the Account qualifies as having its status Changed...
1 SOQL call instead of 3, no DML calls (Trigger.new changes are implictly inserted/updated by SFDC). Works for bulk operations, gets product names from all of each Account (in trigger list) 's Opportunities that have products.
I still feel this would be better done by an after update trigger on Opportunity as whenever an Opportunity productId is added/deleted (change is impossible) in an OpportunityLineItem, the parent Oppo is triggered (because the Amount rolls up) - then your Account is always sync'ed when Oppos changed rather than sync'ing when the account changes.
Thanks so much!!!
this trigger worked and it brings the product name in the account.
The reason why i am writing the trigger in the account is because
we have a workflow rule in the Account object that triggers an email template when Account Status is changed to 'New' to customers.
we would like to include all the product names Associated to this account's opp in the email as merge field.
like
Thank you for purchasing following products
{!ProductNameCopy__c}.
In this scenario we need a custom text field to hold all the product names in the custom text field.
Thats the reason i wrote the trigger in the after update so i was assuming i can check the
Account status condition.
For example, if account status='New'
then copy all the product names in the custom text field ProductNameCopy__c).
sfdclearn :
If you would be so kind, please mark as solution.
BTW --
1> You can check the Account.status value in before triggers as the values set by workflow field updates will be available to the before trigger in Trigger.new
2> Doing the sync from the Oppo to the Account has other benefits --
> the Account always has the list of products, regardless of status. This might be useful to you for other use cases.
> if an Oppo or Opportunity Product is changed/added, the Account won't pick up the changes until the next account update
Thank you so much again !!!
just wanted to clarify a small thing.
some of the product name is copied twice.
GenWatt Gasoline 750kW
GenWatt Gasoline 750kW
xyz
Installation: Industrial - High
Installation: Industrial - High
Installation: Industrial - Low
could you please let me know why this some of product names are repeating twice.
Thanks!!!!
sfdclearn:
well, the code I provided didn't do de-dup'ing, this was left as an exercise for the reader <g>
A reason dups are occurring is that an account x has >1 Oppo, each with the same products; or one Oppo has 2+ line items with same products
To de-dup, you'll need to modify the Map to be a Map<ID,Set<String>>, then as each OLI is processed, you add to the set. SFDC will ensure the set is unique. Then, before setting the Account's list of products custom field, run through the Set and concatenate -- you can also copy the set into a list and then sort before concatenating
trigger AccountTrigger on Account(before update, before insert)
{
Map<ID,String> aIdToConcatenatedProductMap = new Map<ID,String> ();
Set<string> pb=new Set<String>();
for (Opportunity o : [select id, accountId, (select id, PricebookEntry.Product2.Name from OpportunityLineItems) from Opportunity where accountId IN : Trigger.new] )
{
String concatenatedProducts = '';
for (OpportunityLineItem oli : o.opportunityLineItems)
{
concatenatedproducts = concatenatedProducts + oli.PricebookEntry.Product2.Name + '\n ';
if (aIdToConcatenatedProductMap.containskey(o.accountId))
{
String concatSoFar=aIdToConcatenatedProductMap.get(o.accountId);
pb.add(concatSoFar);
aIdToConcatenatedProductMap.put(o.accountId ,pb + ' ' + concatenatedProducts);
}
else
{
aIdToConcatenatedProductMap.put(o.accountId, concatenatedproducts );
}
}
}
for (Account a: Trigger.new)
{
if (aIdToConcatenatedProductMap.containsKey(a.id))
a.productNameCopy__c = aIdToConcatenatedProductMap.get(a.id);
}
}
And the product name is now copied as
{SLA: Gold
, {SLA: Gold
, {SLA: Gold
, {SLA: Gold
} GenWatt Gasoline 750kW
} GenWatt Gasoline 750kW
xyz
, {SLA: Gold
} GenWatt Gasoline 750kW
} Installation: Industrial - High
, {SLA: Gold
, {SLA: Gold
} GenWatt Gasoline 750kW
} GenWatt Gasoline 750kW
xyz
, {SLA: Gold
} GenWatt Gasoline 750kW
} Installation: Industrial - High
Installation: Industrial - Low
could you please advise?
I know my code change might be bad as i am new to apex trigger.
Thanks so much!!!
trigger MyAccountTrigger on Account(before update, before insert) {
Map<ID,Set<String>> aIdToUniqueProductSetMap = new Map<ID,Set<String>> ();
// Go through each Oppo and collect a unique set of Product names from all OLI
for (Opportunity o : [select id, accountId,
(select id, PricebookEntry.Product2.Name from OpportunityLineItems)
from Opportunity where accountId IN :Trigger.new] ) {
Set<String> productsInOppoSet = new Set<String> (); // reset our set for this Oppo
for (OpportunityLineItem oli : o.opportunityLineItems)
productsInOppoSet.add(oli.PricebookEntry.Product2.Name);
// With the unique set, add it into our Map associating each account to its unique product set
if (aIdToUniqueProductSetMap.containsKey(o.accountId)) {
Set<String> uniqueProdSet = aIdToUniqueProductSetMap.get(o.accountId); // add this Oppo's unique set to whatever we have so far
uniqueProdSet.addAll(productsInOppoSet);
aIdToUniqueProductSetMap.put(o.accountId,uniqueProdSet); // put it back in the Map
}
else
aIdToUniqueProductSetMap.put(o.accountId,productsInOppoSet); // first Oppo for this Account; simply do a put
}
// All done with Oppos, now put back into each triggered Account
for (Account a: Trigger.new) {
if (aIdToUniqueProductSetMap.containsKey(a.id)) { // only for those Accounts that had Oppos with products
String concatenatedProducts = '';
for (String p : aIdToUniqueProductSetMap.get(a.id))
concatenatedProducts = concatenatedProducts + (concatenatedProducts.length() == 0 ? '' : ' ') + p; // separate with spaces; other delims possible
a.description = concatenatedProducts ; // when the trigger completes, the triggered Accounts get updated
}
}
}
I tried the above code.
It shows the following error.
Error: Compile Error: expecting a semi-colon, found 'uniqueProdSet' at line 14 column 10
oops -- I corrected line 14
I correct this but again it shows error on same line
Error: Compile Error: Illegal assignment from Boolean to SET<String> at line 14 column 7
teach me to type in code while watching a baseball game ... this time, i verified it compiles (reposted above)
thanks a lot ! that worked.
Hi,
I am trying to include a small logic. that is..
if there is more than opportunity product associated to the a single Account's opportunities then use
only one common name for that.
trigger MyAccountTrigger1 on Account(before update, before insert) {
Map<ID,Set<String>> aIdToUniqueProductSetMap = new Map<ID,Set<String>> ();
Set<String> uniqueProdSet= new Set<String>();
// Go through each Oppo and collect a unique set of Product names from all OLI
for (Opportunity o : [select id, accountId,
(select id, PricebookEntry.Product2.Name from OpportunityLineItems)
from Opportunity where accountId IN :Trigger.new] ) {
Set<String> productsInOppoSet = new Set<String> ();
//reset our set for this Oppo
for (OpportunityLineItem oli : o.opportunityLineItems)
productsInOppoSet.add(oli.PricebookEntry.Product2.Name);
// With the unique set, add it into our Map associating each account to its unique product set
if (aIdToUniqueProductSetMap.containsKey(o.accountId)) {
uniqueProdSet = aIdToUniqueProductSetMap.get(o.accountId); // add this Oppo's unique set to whatever we have so far
uniqueProdSet.addAll(productsInOppoSet);
aIdToUniqueProductSetMap.put(o.accountId,uniqueProdSet); // put it back in the Map
}
else
aIdToUniqueProductSetMap.put(o.accountId,productsInOppoSet); // first Oppo for this Account; simply do a put
}
if(uniqueProdSet.size()>1)
{
for(Account a: Trigger.new)
{a.productNameCopy__c='Academic';}
}
else
{
// All done with Oppos, now put back into each triggered Account
for (Account a: Trigger.new)
{
if (aIdToUniqueProductSetMap.containsKey(a.id)) { // only for those Accounts that had Oppos with products
String concatenatedProducts = '';
for (String p : aIdToUniqueProductSetMap.get(a.id))
concatenatedProducts = concatenatedProducts + (concatenatedProducts.length() == 0 ? '' : ' ') + p; // separate with spaces; other delims possible
a.ProductNameCopy__c = concatenatedProducts ; // when the trigger completes, the triggered Accounts get updated
}
}
this works some time and and some time it copies all the product names.
I am looking to populate both Product names and Product families in two different fields on account could you please guide me on this?
Regards,
Nazia