function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Eager-2-LearnEager-2-Learn 

Triggers?

Hello,

 

I have two triggers.  One checks for duplicate opportunities and the auto populates the opportunity name with the account name + the mm/yy format of the effective date. The problem is they do not work together.  The dup checker does not detect what I automatically fill in from the other trigger!  Do I have to pull them together as one trigger or is there a way to create this logic in a class and then have one trigger call that class.  If you could give me some direction it would be appreciated.

 

Best Answer chosen by Admin (Salesforce Developers) 
IanRIanR

Hey Tom,

 

Just as a starting point - have a look at the apex:detail component in Visualforce... this will take an entire layout (as per the current record's record type), with or without related lists (you can configure), and you can put your logic in the extension behind the page.

 

 

HTH, Ian

 

:)

All Answers

AmphroAmphro

You can put the logic in a class, and should if multiple triggers need to use the dup checker. Just make sure the method is static. Then you could return a boolean if the dup checker method found an error or not, or throw an exception and catch it in your trigger. So maybe something like this.

 

Trigger:

 

if (Myclass.dupChecker(possibleDupValue)) {

   System.debug('There is a dup')

} else {

   System.debug('There is not dup')

}

 

MyClass:

 

public static boolean dupChecker(MyType possibleDupValue) {

   //return true if possibleDupValue is a dup, else return false;

}

 

Hope that helps

 

 

Eager-2-LearnEager-2-Learn

Is there a standard format for creating a class that is called from inside a trigger?  In other words, a trigger may have Before Insert, Before Update.  How do I get the class to know what those values are?  I have this trigger below and I would like to convert it to a class and then call that class from the trigger?  That would be the better way to code, yes/no?  I have not found an example in the Developer Guide book that I have.  Can you help me onto the right path, please?

 

trigger FindDuplicateOppName on Opportunity (before insert, before update) {

   Map<String, Opportunity> OppMap = new Map<String, Opportunity>();

   for (Opportunity opp : System.Trigger.new){

      if (opp.Dup_Name_Checker__c) {

         // make sure we do not treat an opportunity name

         // that is not changing during an update as a dup

          if ((opp.Name != null) && (System.Trigger.isInsert ||

            (opp.Name != System.Trigger.oldMap.get(opp.Id).Name))) {

            // make sure another new opportunity is not also a dup

             if (oppMap.containsKey(opp.Name)) {

               opp.Name.addError('Another new Opportunity has the same Name.');            }

else{               oppMap.put(opp.Name, opp);

            }

         }

      }

   }

   // using a single database query, find all the opportunities in

   // the database that have the same name as any of the opportunities

   // being inserted or updated.

    for (Opportunity opp : [SELECT Dup_Name_Checker__c, Name FROM Opportunity WHERE Name in :oppMap.keySet()]) {

      if (opp.Dup_Name_Checker__c) {

         Opportunity newOpp = oppMap.get(opp.Name);          newOpp.Name.addError('A Opportunity with this Name already exists.');

      }

   }

}

AmphroAmphro

Hmm, I'm not sure what you are tring to accomplish... It seems you want to make sure that the Opportunity name does not already exist in the batch if the flag Dup_Name_Checker__c is checked. Is that right?

 

I wouldn't even create a method for the trigger to call. I would do it all in the trigger using a set to keep track of names in the batch

 

Here is an example of what I mean by using a set. This example makes sure the names do not exist in the batch or in ALL opportunities. 

 

trigger FindDuplicateOppName on Opportunity (before insert, before update) {

   Map<String, Opportunity> OppMap = new Map<String, Opportunity>();

   Set<String> dupNameSet = new Set<String>();

   

   for (Opportunity opp : System.Trigger.new){

          if (dupNameSet.contains(opp.Name) {

             opp.Name.addError('Another new Opportunity has the same Name.');

      }

      else {

             oppMap.put(opp.Name, opp);

        dupNameSet.add(opp.Name);

      }


      for (Opportunity opp : [SELECT Name FROM Opportunity]) {         

         if (dupNameSet.contains(opp.Name) {

             oppMap.get(opp.Name).addError('A Opportunity with this Name already exists.');

         }

      }

   }

 

Hope this will get you on the right track. It not I can try to create an example of something a little closer to what you are tring to accomplish.

Eager-2-LearnEager-2-Learn

Ok, here is my real issue.  I have a dupcheck trigger and I put a new field (checkbox) on the Opportunity.  I only did this because I do not know an easy way to have a trigger prompt the user, asking them if they want to enter the duplicat or not.  The check box is my work around.  If you know a way that I can have the user's prompted to accept the dup or cancel it that would be great.  My current dup approach works by itself.

 

The second trigger I have is to auto populate the Opportunity name with the associated account name and append the effective date to the end of that name with a mm/yy format.  For example, "Abc Account 09/09".

 

Now the issue.  When I have both triggers active, the dup check trigger does not work.  I think it is because one, the order the triggers fire are incorrect. Or two, because on the screen the user has to put "*" in the Opportunity name field for the trigger logic to work and auto populate the field (I wrote the logic to work that way) when dup trigger fires, the dup trigger does not see the value of the auto text.  In this example, "Abc Account 09/09", assuming it already exists.  Oh, if you are wondering why I have to have the user put * it is because I just picked that character because Opportunity name is a required field set by SFDC and I cannot find a way to make it not required.  And I found out that the validation behind the seens occurs prior to triggers firing.

 

I hope this helps you see what I am trying to occomplish.  I could paste both triggers in here if you need it to see what I am talking about.

 

Don't give up on:)

 

AmphroAmphro

Ok, so a few questions. Why do you want them to be able to confirm a duplicate name? In fact, why do you even want to have duplicate names?  If your generating the name from the trigger, then your users don't need to know the Opportunity's name?

 

Maybe you could explain a couple scenarios so I can better understand what your want to happen. Then we can try to figure out the best way to do it.

 

However, with that said, it seems to me you want to always auto-populate the Opportunity's Name. Maybe I miss understood and you want to only auto-populate the name if they don't type one in?

 

Either way, I would recommend doing this from a controller. If they don't type in an opportunity name, your controller can see that and auto-populate one for them before doing the insert. Or if you want to generate one no mater what, you can not even display the field and still generate it in the controller.This will also make it so your users don't have to enter a '*' which is a bad user experience and a pain.

 

Your controller can handle duplicates as well, but again, I still don't understand why you want them...

 

Anyways, we getting closer?

Eager-2-LearnEager-2-Learn

Since I am so new to Apex/SFDC controller sounds complicated!  Is it?  You are correct in that if the user enters a name then I do not want to auto populate.  Since the standard SFDC layout requires a value in the name field, I came up with "*" just to satisfy that requirement and allow me to perform the auto populate routine.

 

As far as why the business wants duplicates for certain situations is unknown to me at this time but I was told that there are time.  I sent an email out to the user community in hopes to get the business reason why they want dups sometimes.  As soon as I get a response I will fill you in.

 

Controller: Sounds like something that would be the best approach; however, do we know how much time something like that would take?  It would have to mimic the current screen layout.  I guess you can give me a starting point and I will refer to the Developer Guide as well, that would be great.

 

Below is what I have as a trigger that handles the auto populate and dup check all in one.  So far my manual tests interacting with the screen is 100% working; however, I agree that it is a little wacky to use a check box for dup checking and a * for auto populate.  So again, if you could get me on the right track that would be appreciated very much.

 

trigger OppDupCheck_AutoCreateOppName on Opportunity (before insert, before update) { //********************************************************* // // Checks for duplicates and auto populates opp name field // with account name + mm/yy from the effective date // //********************************************************* // Create a unique set of Account ids we will be querying Set<Id> accIds = new Set<Id>(); // Create a map to store the Account ID's and Names Map<Id, Account> accounts; Map<String, Opportunity> OppMap = new Map<String, Opportunity>(); for(Opportunity opp :Trigger.new){ if (Trigger.isInsert){ // Save this Account ID - we will need it later accIds.add(opp.AccountId); } } // Query the Account table to get all the accounts associated with the Opportunity(s) if (Trigger.isInsert){ accounts = new Map<id, Account>([SELECT Id, Name FROM Account WHERE Id in :accIds]); } for(Opportunity opp :Trigger.new){ if(Trigger.isInsert){ String currentName = opp.Name; String effectiveDate = opp.Effective_Date__c.format(); Integer removeMMYY; Integer currentLength = currentName.length(); DateTime dt = DateTime.newInstance( opp.Effective_Date__c.year(), opp.Effective_Date__c.month(), 1, 0, 0, 0); string fmtDate = dt.format('MM/yy'); //currentName = accounts.get(opp.AccountId).Name; if (currentName == '*' || currentLength <= 3){ // user did not enter at least three characters for an opportunity name // It is assumed that we will auto populate the currentName with opp.Account // and append the mm/yy. currentName = currentName + ' ' + fmtDate; }else{ // User must have typed there own opp.Name // Let us see if they include mm/yy If (currentName.substring(currentLength - 3, currentLength - 2) <> '/'){ // We need to append the mm/yy because the user did not include it currentName = currentName + ' ' + fmtDate; } } opp.Name = currentName; } if (opp.Dup_Name_Checker__c) { // make sure we do not treat an opportunity name // that is not changing during an update as a dup if ((opp.Name != null) && (System.Trigger.isInsert || (opp.Name != System.Trigger.oldMap.get(opp.Id).Name))) { // make sure another new opportunity is not also a dup if (oppMap.containsKey(opp.Name)) { opp.Name.addError('Another new Opportunity has the same Name.'); }else{ oppMap.put(opp.Name, opp); } }else if ((opp.Name != null) && (System.Trigger.isUpdate)) { oppMap.put(opp.Name, opp); } } } // using a single database query, find all the opportunities in // the database that have the same name as any of the opportunities // being inserted or updated. for (Opportunity opp : [SELECT Dup_Name_Checker__c, Name FROM Opportunity WHERE Name in :oppMap.keySet()]) { if (opp.Dup_Name_Checker__c) { Opportunity newOpp = oppMap.get(opp.Name); newOpp.Name.addError('A Opportunity with this Name already exists.'); } } }

 

 

Thanks

AmphroAmphro

Hey Tom,

 

Going the controller route would definitely be more involve. You would have to ensure the layout looks the same. Also, handling batches is another story. For example, if edits are enabled in the the list layout view, you would have to override that as well. Also it would not check edits through the API, lets say Excel Connector.

 

So it depends the situation and what your users want. If it is just a convenience thing through the New or Edit page, then the controller would be a better option. I can go into more detail if that does sound like what you want.

 

However, if your trigger is working, you may not want to deal with it. I do have some suggestions on your code though, if you want them. They are typically good coding practices but not necessary. They may also help the speed up your code. Which for cloud computing is never a bad thing and the littlest speed enhancements can matter. Also, how much did you test this? I can see some areas in your code for potential bugs/ I'll try to point them out.

 

You check isInsert a lot in your code. Why not just have one check in the beginning. This way you don't have to check for each time when it is not dependent on the record. The trigger will either be before insert, or before update. Some of your other checks can be minimized as well. For example, in the second loop, I believe it will be put in the oppMap when opp.Name is not null unless oppName != System.Trigger.oldMap.get(op.Id).Name. I don't even know why you do this check. Who cares about the old values of Name. It should be sufficient just to check if the new names are duplicates. Because what if they are swapping two names? Then you would get an error. Unless that what you want.

 

I see your comment says you don't want to touch names that have not been changed. Well, if it hasn't been changed it will still get added to the oppMap. Then if another record tries to give it that same name, it will throw an error because you checked the oppMap, which the other name was already added.

 

Also, you create a bunch of variables in your loop. I don't know if apex optimizes and reuses them, but if it doesn't, then you are wasting time allocating those variables everytime when you don't need too. 

 

Here are some of the changes I mean. 

 

trigger OppDupCheck_AutoCreateOppName on Opportunity (before insert, before update) {
//*********************************************************
//
// Checks for duplicates and auto populates opp name field
// with account name + mm/yy from the effective date
//
//*********************************************************
// Create a unique set of Account ids we will be querying
Set<Id> accIds = new Set<Id>();
// Create a map to store the Account ID's and Names
Map<Id, Account> accounts;
Map<String, Opportunity> OppMap = new Map<String, Opportunity>();

if (Trigger.isInsert){
for(Opportunity opp :Trigger.new){
// Save this Account ID - we will need it later
accIds.add(opp.AccountId);
}
// Query the Account table to get all the accounts associated with the Opportunity(s)
accounts = new Map<id, Account>([SELECT Id, Name FROM Account WHERE Id in :accIds]); }

String currentName;
Integer currentLength;
String fmtDate;
for (Opportunity opp :Trigger.new) {
currentName = opp.Name;
currentLength = currentName.length();
string fmtDate = DateTime.newInstance( opp.Effective_Date__c.year(),
opp.Effective_Date__c.month(), 1, 0, 0, 0).format('MM/yy');

//currentName = accounts.get(opp.AccountId).Name;
if (currentName == '*' || currentLength <= 3){
// user did not enter at least three characters for an opportunity name
// It is assumed that we will auto populate the currentName with opp.Account
// and append the mm/yy.
currentName = currentName + ' ' + fmtDate;
} else {
// User must have typed there own opp.Name
// Let us see if they include mm/yy
if (currentName.substring(currentLength - 3,currentLength - 2) <> '/') {

// We need to append the mm/yy because the user did not include it
currentName = currentName + ' ' + fmtDate;
}
}
opp.Name = currentName;

// make sure we do not treat an opportunity name
// that is not changing during an update as a dup
// make sure another new opportunity is not also a dup
if (opp.Dup_Name_Checker__c)
if (opp.Name != null && oppMap.containsKey(opp.Name)) {
opp.Name.addError('Another new Opportunity has the same Name.');
} else {
oppMap.put(opp.Name, opp);
}
}
}
}

// using a single database query, find all the opportunities in
// the database that have the same name as any of the opportunities
// being inserted or updated.
for (Opportunity opp :
[SELECT Dup_Name_Checker__c, Name FROM Opportunity WHERE Name in :oppMap.keySet()]) {
if (opp.Dup_Name_Checker__c) {
oppMap.get(opp.Name).Name.addError('A Opportunity with this Name already exists.');
}
}

}

 

So make sure I didn't mess up any of your code. I took out things I thought was unnecessay but they could have been.

You don't even have to bother making any of these changes, just thought I share :) 

Eager-2-LearnEager-2-Learn

Hi Amphro,

 

Great advise on the variables declared in the wrong spot.  That is 101 stuff and I should have caught that; however, I guess with all the other new things to think about it snuck by me. :)  I made some of the code changes per your suggestion and this is how it works.  I will present it to my sup either at the end of this week or the beginning of next week.

CHECKBOX checked:

If I create a new record and I make a known dup it gets detected.

If I create a clone and save the clone with no changes the dup is detected.

If I edit the existing record and save without changes it does not get detected as a dup.  This makes sense to me, good.

 

CHECKBOX unchecked:

If I create a new record and make a known dup it gets saved.

If I create a clone and save the clone with no changes it gets saved.

If I edit the existing record and save without changes it gets saved.

 

 

Now, should this idea get shot down and the check box is not desired then I may have to consider a controller!!!

Is there a wizard of some kind that can take an existing layout and save it as a visualforce page so that you get to start from the exact layout and then just add on your own functionality?

 

I am not a Java/Java script programmer so I am looking for a magic wand! :)

 

 

IanRIanR

Hey Tom,

 

Just as a starting point - have a look at the apex:detail component in Visualforce... this will take an entire layout (as per the current record's record type), with or without related lists (you can configure), and you can put your logic in the extension behind the page.

 

 

HTH, Ian

 

:)

This was selected as the best answer
Eager-2-LearnEager-2-Learn
Thanks, I will check into details component.  In the mean time, can you help me with another post that I have out there about test scripts and inserting records into the Opportunity object?  I need to insert 201 records that are not duplicates.