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
RelaxItsJustCodeRelaxItsJustCode 

Another mind bender in need of a True apex developer, Please help....

Ok I came up a trigger that is thin but does specifically what I need to do. Plus it works with the class in this post.

 

I have a few questions though, Seeing that the class is "CaseFunctions" we should be able to add even more case related functionality. I'm not so sure I'm using the trigger's variables, Trigger.New and Trigger.NewMap correctly.

 

I would like the trigger and class to be able to compare the oldMap as well as the newMap when we address adding more functions to this class.

 

Also, I need an opion on the class, it is currently only applies to one use case I can think of but it may be worth while to split it into separate functions later but that said,

 

How in a single trigger would I call the separate functions at the same time or should I just build three separate triggers.... Which would be the best approach in your thoughts???

 

Also, I would like to build an escape for the current user's profile "Name" for example "System Administrator". I have no idea how to achieve these things I've tried to work it out myself but unfortunately I haven't found the correct approach. Please help if you can field this one.

 

Thanks,

Steve Laycock

 

trigger CaseTriggerBeforeComplete on Case (before update)
{
      CaseFunctions.CaseValidationBeforeComplete(Trigger.new, Trigger.newMap);
}

 

public class CaseFunctions
{
   public static void CaseValidationBeforeComplete(Case[] caseRecs, Map<ID,Case> newMap)
   {
        List<Id> caseIds = new List<Id>();
        
        Id caseRecordTypeId = [SELECT Id FROM RecordType WHERE Name = 'PM Case' AND SObjectType = 'Case'].Id;

        Id TaskRecordTypeId = [SELECT Id FROM RecordType WHERE Name = 'PM Task' AND SObjectType = 'Task'].Id;
        
        for (Case c : caseRecs)
        {
            Case newRec = newMap.get(c.Id);
            if(c.RecordTypeId == caseRecordTypeId && (c.Status == 'Completed' || c.Status == 'Closed'))
            {
                 caseIds.add(c.Id);
            }
        }
        for(Training_Information__c customobj : [Select Id, Status__c,Case__c From Training_Information__c Where Case__c in: caseIds])
        {
            if(customobj.Status__c == 'Pending' || customobj.Status__c == 'Scheduled')
            {    
                 newMap.get(customobj.Case__c).addError('Training has not been completed.');
            }     
        }    
        
        for(Development_Request__c customobj : [Select Id, Stage__c,Case__c From Development_Request__c Where Case__c in: caseIds])
        {
            if(customobj.Stage__c != 'Completed' && customobj.Stage__c != 'Cancelled DR/ VOID' && customobj.Stage__c != 'Draft')
            {
                newMap.get(customobj.Case__c).addError('There are open Development Request associated with this Case.');
            }
        }  

        for(Task T : [Select Id, Status, WhatId, RecordTypeId From Task Where WhatId in: caseIds])
        {
            if(T.RecordTypeId == TaskRecordTypeId && (T.Status != 'Completed' && T.Status != 'Discarded'))
            {
                newMap.get(T.WhatId).addError('There are still open PM Task on this Case.');
            }
        }
    }    
}

 

Best Answer chosen by Admin (Salesforce Developers) 
Andrew WilkinsonAndrew Wilkinson

So you want a way to load data and not have the Trigger fire? Usually I have accompllished this with custom settings where the trigger checks for a specific value with its trigger name. In this way you can "turn triggers on/off" when needed. If you wanted to do it by profile though...you could check using the UserInfo object and methods.

 

Profile p = [SELECT Name FROM Profile WHERE ID =:UserInfo.getProfileId()];

 

then use the profile in your validation...

 

if(p.Name != 'System Administrator').

 

Now if you wanted it off only on specific occasions then you would want to use custom settings. Create one titled Triggers or whatever. Create an Integer field that the value will be 1 or o and a trigger name or method name field(which will be used in the query). Then in your Trigger you can query the custom settings based on the trigger name and check that the value is 1 or 0 whichever you would prefer. Then on the config side you can turn the trigger on or off in production pseudo style by changing the integer value in custom settings.

 

 

All Answers

Andrew WilkinsonAndrew Wilkinson

If you wanted to compare the old and new map you can pass it into the method as follows:

(Map<Id,Case> oldMap, Map<Id,Case> newMap) as your method parameters. Then pass Trigger.OldMap and Trigger.NewMap intol the method from your Trigger. You can compare values by iterating through one of the values of the map and calling get on the other. For example:

 

for(Case c : newMap.values()){

if(c.Type != oldMap.get(c.Id)){

    //do something

}

}

 

For splitting the functionality of the one method into several...really this is a question you need to answer. There are two things to consider: is there functionality in there that you would want to reuse as a standalone method? And long methods are against best practices. For example, having a 1000 line method does not follow best practices...its ugly...its hard to follow through if you didn't write it...its harder to write unit tests for...this is extreme but just saying.

 

To add more functionality add another method and pass the maps or lists in again. Keep the methods in the same class(if they are relevant to the case...at times there may be situations in which you need to use methods of other objects in which case you could pass them into a service class for that object) and just use the same trigger. The logic you can use in your trigger should be simple such as if(Trigger.isInsert) or if(Trigger.isAfter) to have specific methods tied to specific events. All of your methods will not always need to be fired on every event in the trigger.

 

As for using the Trigger.newMap and Trigger.New just use one or the other. If you need to use get methods on specific records or compare to old values then use the oldMap and newMap. If you just need to work with the list then pass newMap. When you want to iterate over values and all you passed is the Trigger.NewMap you can use Trigger.newMap.values() to get a list that is identical to the Trigger.New list. There is no need to pass both Trigger.New and Trigger.NewMap into your method.

 

As far as the escape requirement...I don't quite understand what you mean. Can you give more specific examples?

 

 

RelaxItsJustCodeRelaxItsJustCode

The escape is to enable service users with the admin profile or admins to Salesforce not to be held to the validation in the class.  So for example if I wanted to close several cases via the dataloader but didn't want to be held hostage to making sure all data lived up to the class validation then if Profile Name <> System Admin for example...

Andrew WilkinsonAndrew Wilkinson

So you want a way to load data and not have the Trigger fire? Usually I have accompllished this with custom settings where the trigger checks for a specific value with its trigger name. In this way you can "turn triggers on/off" when needed. If you wanted to do it by profile though...you could check using the UserInfo object and methods.

 

Profile p = [SELECT Name FROM Profile WHERE ID =:UserInfo.getProfileId()];

 

then use the profile in your validation...

 

if(p.Name != 'System Administrator').

 

Now if you wanted it off only on specific occasions then you would want to use custom settings. Create one titled Triggers or whatever. Create an Integer field that the value will be 1 or o and a trigger name or method name field(which will be used in the query). Then in your Trigger you can query the custom settings based on the trigger name and check that the value is 1 or 0 whichever you would prefer. Then on the config side you can turn the trigger on or off in production pseudo style by changing the integer value in custom settings.

 

 

This was selected as the best answer