This is a code to create a new task when stage is inserted or updated to Closed Won
trigger ClosedOpportunityTrigger on Opportunity (after insert, after update) {
List<Task> tl = new List<Task>();
for(Opportunity op : Trigger.new) {
if(Trigger.isInsert) {
if(Op.StageName == 'Closed Won') {
tl.add(new Task(Subject = 'Follow Up Test Task', WhatId = op.Id));
}
}
if(Trigger.isUpdate) {
if(Op.StageName == 'Closed Won'
&& Op.StageName != Trigger.oldMap.get(op.Id).StageName) {
tl.add(new Task(Subject = 'Follow Up Test Task', WhatId = op.Id));
}
}
}
if(tl.size()>0) {
insert tl;
}
}
Here, what does && Op.StageName != Trigger.oldMap.get(op.Id).StageName) do? Why do we use oldMap here?
Trigger.newMap is the map of IDs of new object values. Available in insert, update, and undelete triggers, and 'new' records can only be modified in before triggers.
Trigger.oldMap is the map of IDs of old object values. Available in update and delete triggers only.
if (Trigger.isUpdate) {
// Iterate updated opportunities
for (Opportunity o : Trigger.new) {
// Get the opportunity before update
Opportunity oldOpp = Trigger.oldMap.get(o.Id);
// Check if a value changed
if (o.Some_Value__c == oldOpp.Some_Value__c) {
System.debug('Value did not change.');
} else {
System.debug('Value changed!');
}
}
}
Note: I could have used Trigger.newMap instead of Trigger.new but I'd be looping through Trigger.newMap.values() instead - with the same end result. newMap is just a convenient way of getting the bulkified data in map form instead of a list.
We use old map to compare with the new value of the Some_Value__c custom field. If the two values differ then the field value has changed. Of course, if you read the code in the two if branches, this is obvious.
Related
Im trying to update BilingCountry in Salesforce via the Bulk API from a csv, there are 1025 entries in the csv file. The Account ids in the csv have a BillingCountry different to what is in Salesforce but i get the following error:
CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY:test_tr_U: System.LimitException: Too many DML rows: 10001:--
Here is my Trigger:
Trigger test_tr_U on Account (after update)
{
if(checkRecursion.runOnce())
{
set<ID> ids = Trigger.newMap.keyset();
List<TestCust__c> list1 = new List<TestCust__c>();
for(ID id : ids)
{
for (Account c: Trigger.new)
{
Account oldObject = Trigger.oldMap.get(c.ID);
if (c.Billing_Country__c!= oldObject.Billing_Country__c ||
c.BillingCountry!= oldObject.BillingCountry )
{
TestCust__c change = new TestCust__c();
change.Field1__c = 'update';
change.Field2__c = id;
change.Field3__c = false;
change.Field4__c = 'TESTCHANGE';
list1.add(change);
}
}
}
Database.DMLOptions dmo = new Database.DMLOptions();
dmo.assignmentRuleHeader.useDefaultRule = true;
Database.insert(list1, dmo);
}
}
Looping over every id then over every updated account will result in the creation of #id * #account. Those are the same value so you'll create #account^2 (1050625) TestCust__c records.
Salesforce splits the 1025 records in chunk of 200 records and since a Bulk API request caused the trigger to fire multiple governor limits are reset between these trigger invocations for the same HTTP request.
Anyway for each trigger run you're creating 200*200 = 40000 TestCust__c records, which are highly above the limit of 10000 records per transaction, therefore the system will raise LimitException.
You should remove the outer loop: it's simply wrong.
Trigger test_tr_U on Account (after update)
{
if(checkRecursion.runOnce())
{
List<TestCust__c> list1 = new List<TestCust__c>();
for (Account c: Trigger.new)
{
Account oldObject = Trigger.oldMap.get(c.Id);
if (c.Billing_Country__c != oldObject.Billing_Country__c ||
c.BillingCountry!= oldObject.BillingCountry)
{
TestCust__c change = new TestCust__c();
change.Field1__c = 'update';
change.Field2__c = c.Id;
change.Field3__c = false;
change.Field4__c = 'TESTCHANGE';
list1.add(change);
}
}
Database.DMLOptions dmo = new Database.DMLOptions();
dmo.assignmentRuleHeader.useDefaultRule = true;
Database.insert(list1, dmo);
}
}
I have three objects
1)Truck__c
2)Booking__C
3)Payment___C.
Truck and Booking has master detail relationship where Truckk is master and Booking is detail.
Booking and payment has lookup relationship where Booking is parent and payment is child.
I want to update a field present isBooked(checkbox) in Truck based on the value present in a field remainingAmount in payment object. I have added trigger on Payment object like below
trigger TestTrigger on Payment__c (after insert) {
if(Trigger.isAfter && Trigger.isUpdate){
for (Payment__c pay:Trigger.New)
{
payId.add(pay.id);
paidAmount =Integer.valueOf(pay.Paid_Amount__c);
}
Map <id,Booking__c> bookingMap = new Map <id,Booking__c> ();
for (Booking__c obj: [Select Id, Booking_ID__c from Booking__c where Booking_ID__c in:payId])
{
bookingMap.put(obj.Booking_ID__c, obj);
}
for(Booking__c objBooking:matchingIdsMap){
Truck__c truckObj = [Select id,Price__c from Truck__c where Truck__c.id = objBooking.Truck__c.id];
//Integer paidAmount = Payment__c.Paid_Amount__c;
Integer totalAmount = Integer.valueOf(truckObj.Price__c);
Integer remainingAmount = totalAmount-paidAmount;
If(remainingAmount == 0){
truckObj.Booked__c = true
}
update truckObj;
}
}
}
Here first I am getting payment ID's and based on that I am fetching Booking objects which is lookup parent of payment. After this I am trying to fetch the truck object which is master of Booking. But I don't know how to query on this as where clause in query giving error
Truck__c truckObj = [Select id,Price__c from Truck__c where Truck__c.id = objBooking.Truck__c.id];
Please note there is no direct relationship between Truck and Payment
How can I fetch truck object
Thanks in Advance
Short answer: while referring to parent fields, you should use the relationship name, so Truck__r instead of Truck__c.
Anyway it is not the only problem with that code.
Long answer:
your trigger is in after insert, but you check for an after update event: if(Trigger.isAfter && Trigger.isUpdate). Probably you want to run this trigger in both after insert and after update.
You never declared payId nor paidAmount which you use in your first for-loop. Anyway paidAmount would just hold the last value, which probably you won't need.
[Select Id, Booking_ID__c from Booking__c where Booking_ID__c in:payId] this query should return an empty list because in payId you've stored the ids of Payment__c, that is a child of Booking__c, while in the first loop you should have stored the ids of parents Booking__c
[Select id,Price__c from Truck__c where Truck__c.id = objBooking.Truck__c.id] Here there is no reason to write where Truck__c.id It should be just WHERE Id = :objBooking.Truck__c.
Beware: putting a SOQL in a loop you will easily hit the Governor Limit about SOQL, which will raise a System.LimitException: Too many SOQL queries: 101.
The same goes by putting a DML in a loop.
I'm going to assume that the API Name of the lookup fields is the same of the parent object, so that a Booking__c field exists on Payment__c object and a Truck__c exists on Booking__c object.
If I got the logic right about how setting the flag on Truck object, this should be the trigger code.
trigger TestTrigger on Payment__c (after insert, after update) {
if(Trigger.isAfter && (Trigger.isInsert || Trigger.isUpdate)) {
Map<Id, List<Payment__c>> mapBookingIdPaymentList = new Map<Id, List<Payment__c>>();
for (Payment__c pay : Trigger.New) {
List<Payment__c> paymentList = mapBookingIdPaymentList.get(pay.Booking__c);
if (paymentList == null) {
paymentList = new List<Payment__c>();
mapBookingIdPaymentList.put(pay.Booking__c, paymentList);
}
paymentList.add(pay);
}
Map<Id, Decimal> mapTruckPrice = new Map<Id, Decimal>();
Map<Id, Integer> mapTruckRemainingAmount = new Map<Id, Integer>();
for (Booking__c booking: [SELECT Id, Truck__c, Truck__r.Price__c FROM Booking__c WHERE Id IN :mapBookingIdPaymentList.keySet()]) {
mapTruckPrice.put(booking.Truck__c, booking.Truck__r.Price__c);
Integer sumOfRemainingAmount = mapTruckRemainingAmount.containsKey(booking.Truck__c) ? mapTruckRemainingAmount.get(booking.Truck__c) : 0;
for (Payment__c pay : mapBookingIdPaymentList.get(booking.Id)) {
sumOfRemainingAmount += pay.Paid_Amount__c != null ? pay.Paid_Amount__c.intValue() : 0;
}
mapTruckRemainingAmount.put(booking.Truck__c, sumOfRemainingAmount);
}
List<Truck__c> trucksToUpdate = new List<Truck__c>();
for (Id truckId : mapTruckPrice.keySet()) {
// There is no need to query a record just to update it if you already have its Id.
Truck__c truck = new Truck__c(Id = truckId);
truck.Booked__c = mapTruckPrice.get(truckId) - mapTruckRemainingAmount.get(truckId) == 0;
trucksToUpdate.add(truck);
}
update trucksToUpdate; // dml outside the loop
}
}
By the way, you should move the logic in an handler class, following the best practices.
I have written an APEX Class that sends an email when a client is released. There is a method that I thought I had bulkified but I was told it does not. This is because this method calls another function which actually does the actual email creation and that is not bulkified. Can someone guide me as to how the SOQL queries can be taken out of the method?
global class LM_ChangeAccountRT {
private static final Profile sysAdmin = [select id from profile where name='System Administrator'];
#AuraEnabled
public static String resendEmails(List<String> accountIdList) {
String response = null;
try {
//Only send emails if user is either an ARMS Administor or System Administrator
if (System.label.ARMS_Administrator_Profile_Id == userinfo.getProfileId() ||
sysAdmin.Id == userinfo.getProfileId()) {
List<Account> accList = [SELECT Id,Client_Released__c, RecordTypeId,Client_Number__c, Client_Released__c, Email_Sent__c FROM Account WHERE Id IN:accountIdList];
for(Account acc: accList){
if (acc.Client_Number__c != null && acc.Client_Released__c && acc.Email_Sent__c == true) {
sendpdfgenerationEmails(acc); //this is the method thats not bulkified.
acc.Email_Sent__c = false;
response = 'Email Sent';
}else {
response= 'Access Denied';
}
}
update accList;
}
}catch(Exception e) {
System.debug(e.getMessage());
response = 'Error sending emails';
}
return response;
}
public static void sendpdfgenerationEmails(Account acc){
system.debug('start of confirmation card and pdf generation');
//Logic to find which VF template is used to send an email.
list<EmailTemplate> templateId = new list<EmailTemplate>();
string temppartner;
String partner_opt_in_attachment;
boolean sendFCAmail;
List<Dealership_PDF_Generation__c> custsettingdata = Dealership_PDF_Generation__c.getall().values();
System.debug('custom setting size = ' + custsettingdata.size());
// Fetch State
if(acc.Dealership_State__c!=null && acc.Dealership_Partner__c!=null)
{
for(Dealership_PDF_Generation__c tempcustsetting :custsettingdata)
{
if(acc.Dealership_Partner__c == tempcustsetting.Dealership_Partner__c && acc.Dealership_State__c==tempcustsetting.State__c && tempcustsetting.State__c=='WA' && acc.Dealership_State__c=='WA'){
//For WA State
// temppartner= '%' + tempcustsetting.TEMPLATE_Unique_name__c + '%';
temppartner= tempcustsetting.TEMPLATE_Unique_name__c;
if(acc.Dealership_Spiff_Payment__c == '% premium'){
partner_opt_in_attachment=tempcustsetting.opt_in_form_premium__c;
}else{
partner_opt_in_attachment=tempcustsetting.opt_in_form_nonpremium__c;
}
}
else if(acc.Dealership_Partner__c == tempcustsetting.Dealership_Partner__c && acc.Dealership_State__c==tempcustsetting.State__c && tempcustsetting.State__c=='TX' && acc.Dealership_State__c=='TX'){
//For TX State
//temppartner= '%' + tempcustsetting.TEMPLATE_Unique_name__c + '%';
temppartner= tempcustsetting.TEMPLATE_Unique_name__c;
if(acc.Dealership_Spiff_Payment__c == '% premium'){
partner_opt_in_attachment=tempcustsetting.opt_in_form_premium__c;
}else{
partner_opt_in_attachment=tempcustsetting.opt_in_form_nonpremium__c;
}
}
else if(acc.Dealership_Partner__c == tempcustsetting.Dealership_Partner__c && acc.Dealership_State__c!=tempcustsetting.State__c && tempcustsetting.State__c!='TX' && acc.Dealership_State__c!='TX' && acc.Dealership_State__c!='WA' &&tempcustsetting.State__c!='WA' ){
//For Non TX State
//temppartner= '%' + tempcustsetting.TEMPLATE_Unique_name__c + '%';
temppartner= tempcustsetting.TEMPLATE_Unique_name__c;
if(acc.Dealership_Spiff_Payment__c == '% premium'){
partner_opt_in_attachment=tempcustsetting.opt_in_form_premium__c;
}else{
partner_opt_in_attachment=tempcustsetting.opt_in_form_nonpremium__c;
}
system.debug('grabbed template: ' + temppartner);
}
if(acc.Dealership_Partner__c != null && temppartner!=null ){
templateId.add([Select id,DeveloperName from EmailTemplate where DeveloperName = :temppartner]); //This will probably cause governor limit issues. First problem
}
if (partner_opt_in_attachment != null) {
StaticResource sr = [Select s.Name, s.Id, s.Body From StaticResource s where s.Name =: partner_opt_in_attachment]; //'static_resource' is the name of the static resource PDF. This is another SOQL query that will cause problems
Blob tempBlob = sr.Body;
Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
efa.setBody(tempBlob);
efa.setFileName('Opt-in.pdf');
List<Messaging.EmailFileAttachment> attachments = new List<Messaging.EmailFileAttachment>();
attachments.add(efa);
// add attachment to each email
for (Messaging.SingleEmailMessage email : emails) {
email.setFileAttachments(attachments);
}
}
system.debug('email sent: ' + emails.size());
Messaging.sendEmail(emails);
}
}
}
The reason why I am trying to bulkify this is because I have written a APEX scheduler that calls the resendemails method everyday at 7am to check which records need to have an email sent. I am afraid that if there are more than a 100 clients then it will cause problems and not send the emails. Any suggestions on how I can optimize the sendpdfemailgeenration() method?
Thank you
Yes, you are right - your's resendEmails() method is not bulkified.
Firstly, let me explain you why is that:
SOQL to get Accounts
Loop 1 on List of Account records
Call sendpdfgenerationEmails() method
Retrieve list of Dealership_PDF_Generation__c records
Loop 2 on List of Dealership_PDF_Generation__c records
SOQL to get StaticResources - Very bad! It's inside double loop!
Call Messaging.sendEmail() method - Very bad! It's inside double loop!
Update on List of Account records
You need to remember that:
1. You should never do SOQLs in loops! - Limit 100 SOQLs per transaction
2. You should never call Messaging.sendEmail() in loops! - Limit 10 calls per transaction
Now let me guide you how to refactor this method:
#AuraEnabled
public static String resendEmails(List<String> accountIdList) {
// 1. SOQL for List of Account records
// 2. Retrieve list of Dealership_PDF_Generation__c records
// 3. SOQL for List of StaticResources for all Names from Dealership_PDF_Generation__c records
// 4. Declaration of new List variable for Messaging.SingleEmailMessage objects
// 5. Loop 1 on List of Account records
// 6. Call new "prepareEmailsForAccount()" method, which prepares and returns list of Messaging.SingleEmailMessage objects
// 7. Add returned Messaging.SingleEmailMessage objects to list from point 4
// 8. End of loop 1
// 9. Call "Messaging.sendEmail()" method with list from point 4
// 10. Update on List of Account records
}
With this you will avoid SOQLs and calling Messaging.sendEmail() method in loops.
I have a scenario where i need to clone a opportunity and its lineitems when the contract End date is today. The opp line item has field called Product_Family_c. i would have to clone only those opp whose lineitems have renewaltype_c as monthly.
I am stuck at how i can clone over the oli items and assign the new oppty ids to the oli items.
todays=date.today();
system.debug('todays'+todays);
for(opportunity o:[select Auto_Renew__c,Contract_lenght_in_months__c,Contract_End_Date__c,id from opportunity where Auto_Renew__c='Yes' and Contract_End_Date__c =:todays ])
{
SetOppId.add(o.id);
MapOpp.put(o.id,o);
}
system.debug('SetOppId'+SetOppId);
system.debug('MapOpp'+MapOpp);
for(OpportunityLineItem oli:[select OpportunityId from OpportunityLineItem where Product_Family__c='Monthly' and OpportunityId in :SetOppId])
{
SetOppIdtoRenew.add(oli.OpportunityId);
Mapoli.put(oli.id,oli);
}
system.debug('SetOppIdtoRenew'+SetOppIdtoRenew);
for(id a:SetOppIdtoRenew)
{
//MapOpp.get(a).Contract_End_Date__c=MapOpp.get(a).Contract_End_Date__c.addDays(1);
//MapOpp.get(a).Contract_End_Date__c=MapOpp.get(a).Contract_End_Date__c.addMonths(integer.valueof(MapOpp.get(a).Contract_lenght_in_months__c));
Lstopp.add(new opportunity(name=MapOpp.get(a).name+' renewal '+string.valueof(date.today()),
Contract_lenght_in_months__c=MapOpp.get(a).Contract_lenght_in_months__c,
Contract_End_Date__c=MapOpp.get(a).Contract_End_Date__c.addMonths(integer.valueof(MapOpp.get(a).Contract_lenght_in_months__c))+1,
StageName=MapOpp.get(a).StageName,
CloseDate=MapOpp.get(a).CloseDate
// ,<fieldname>=MapOpp.get(a).<fieldname>... for all the fields you ned to copy over
));
}
system.debug('Lstopp'+Lstopp);
insert Lstopp;
You were thinking in the right direction. However, before you assign OppID to line item you must insert the opportunity. I imagine this kind of breaks your transactional control, but you can always use Database save points to make the whole operation atomic.
For example:
SavePoint sp = Database.setSavepoint();
try {
upsert newOpportunities;
// now create line items and assign IDs
}
catch (Exception ex) {
Database.rollback(sp);
// cleanup
}
In order to map old opportunities to new ones, you need a Map<ID, Opportunity> and a List and fill them with the same new opportunities to eb able to reuse new IDs in a mapping manner (I used pseudo code, if you have trouble interpreting it, let me know)
foreach(oldopp) {
newopp = clone oldopp;
list.add(newopp);
map.put(oldopp.id, newopp);
}
upsert list;
// now we have new oppids for cloned items, use them to map
foreach(oldlineitem) {
newlineitem = clone oldlineitem;
newlineitem.OpportunityId = map.get(oldlineitem.OpportunityId).Id;
...
}
upsert listofnewlineitems;
}
I am writing a trigger on update but i get this error
Save error: Initial term of field expression must be a concrete SObject: LIST.
I am not sure what is causing this. Here is my code
trigger Update_Discount_on_QuoteLineItem on Quote (after update) {
List <QuoteLineItem> QLI = new List <QuoteLineItem>();
Map<id,double> MapSoftwareDiscount= new Map <id,double>();
Map<id,double> MapHardwareDiscount= new Map <id,double>();
Set <id> Qid= new Set<id>();
for (Quote Q: Trigger.new)
{
if (Q.Software_Discount__c!=System.Trigger.oldMap.get(Q.Id).Software_Discount__c ||Q.hardware_Discount__c!=System.Trigger.oldMap.get(Q.Id).hardware_Discount__c )
{
Qid.add(Q.id);
MapSoftwareDiscount.put(Q.id,Q.Software_Discount__c);
MapHardwareDiscount.put(Q.id,Q.hardware_Discount__c);
}
}
QLI=[select id,QuoteId,product_category__c,Discount from QuoteLineItem where QuoteId in :Qid];
for(integer i=0; i <QLI.size();i++)
{
if (QLI[i].product_category__c=='Software')
{
QLI[i].Discount=MapSoftwareDiscount.get(QLI.QuoteId); //**ERRORS OUT HERE**
}
else if(QLI[i].product_category__c=='Hardware')
{
QLI[i].Discount=MapHardwareDiscount.get(QLI.QuoteId);
}
}
update QLI;
}
I figured out what the issue was. I should have been using
QLI[i].Discount=MapSoftwareDiscount.get(QLI[i].QuoteId);
I didn't include the [i] in the get(QLI[i].QuoteId).