Test Class in Salesforce for a Trigger - salesforce

Good day Everybody,
I am attempting to write a test class for a trigger I helped write. The trigger uses a field called trigger_help__c, a formula field derived from adding the opportunity Type and Account ID, and fires before insert if an Opportunity of that type has been created on that account within the last 90 days. Unless the profile is a system admin. Here is my trigger:
trigger leadDuplicatePreventer on opportunity(before insert) {
set<string> settgs = new set<string>();
list<opportunity> opps = [select id,Trigger_Help__c from opportunity WHERE CreatedDate = LAST_N_DAYS:90];
Profile p=[SELECT ID, Name FROM Profile WHERE Id=:userinfo.getProfileId() Limit 1];
for(opportunity opp : opps){
if(opp.Trigger_Help__c != null && p.Name <> 'System Administrator'){
settgs.add(opp.Trigger_Help__c);
}
}
for(opportunity op : trigger.new){
if(settgs.contains(op.Trigger_Help__c)){
op.adderror('An Opportunity of this type already exists on this Account. Please contact a system administrator with questions.');
}
}
}​
I am having trouble writing the test class and I am clueless as always. I have the following written, but I am lost as to what I actually need to be doing:
#isTest
private class TestleadDuplicatePreventer {
#isTest static void TestleadDuplicatePreventerwithOneOpp() {
Account acct = new Account(Name='Test Account');
insert acct;
Opportunity opp = new Opportunity(Name=acct.Name + ' Opportunity',
StageName='Open Opportunity',
CloseDate=System.today().addMonths(1),
Facility__c='Tacoma WA',
Oppty_Type__c='UCO Service',
AccountID=acct.Id);
insert opp;
Test.startTest();
opp= new Opportunity(Name='Opportunity Test',
StageName='Open Opportunity',
CloseDate=System.today().addMonths(2),
Facility__c='Tacoma WA',
Oppty_Type__c='UCO Service',
Account=[SELECT ID from Account WHERE Account.Name='Test Account']);
try
{
insert opp;
}
catch(Exception duplicate)
{
}
Test.stopTest();
}
}​
Any and all help is appreciated!!

Not sure exactly what your requirements were for your project, you can probably get this done without code doing a rollup sum field that counts the opportunities tied to an account that has the type used in Trigger_Help__c and then put a validation on opportunity that when ISNEW() if Account.Count_Of_Type__c > 0 causes the validation to fire OR on opportunity create a hidden field that is unique that is the concatenation of the account id and the Opportunity type which can be set by workflow or the process builder. Tis would prevent duplicate types from being added for a given account.
But to your original question: In your test I dont see you set the Trigger_Help__c field unless that is set by automation. Also when creating your second opp you dont need to do the query of account you can just use the acct.Id from earlier in the code.
For your catch you need to assert that the e.getMessage() is the message you expect from your add error in the trigger.
You probably also need to do a separate where you runAs(admin_user) where admin_user is a user that you create with the System administrator profile to ensure that you dont get an error when you run as the admin user, along the lines of:
#isTest
private class TestleadDuplicatePreventer {
#isTest static void TestleadDuplicatePreventerwithOneOpp() {
Account acct = new Account(Name='Test Account');
insert acct;
Opportunity opp = new Opportunity(Name=acct.Name + ' Opportunity',
StageName='Open Opportunity',
CloseDate=System.today().addMonths(1),
Facility__c='Tacoma WA',
Oppty_Type__c='UCO Service',
AccountID=acct.Id);
insert opp;
Test.startTest();
opp= new Opportunity(Name='Opportunity Test',
StageName='Open Opportunity',
CloseDate=System.today().addMonths(2),
Facility__c='Tacoma WA',
Oppty_Type__c='UCO Service',
AccountId=acct.Id);
try
{
insert opp;
}
catch(Exception duplicate)
{
System.assertEquals('An Opportunity of this type already exists on this Account. Please contact a system administrator with questions.', duplicate.getMessage())
}
//this should probably be in a separate test method
Profile p = [SELECT Id FROM Profile WHERE Name='System Administrator'];
User u2 = new User(Alias = 'newUser', Email='newuser#testorg.com',
EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US',
LocaleSidKey='en_US', ProfileId = p.Id,
TimeZoneSidKey='America/Los_Angeles', UserName='newuser#testorg.com');
System.runAs(u2) {
insert opp;
}
Test.stopTest();
}
}​

Related

salesforce update trigger on serviceterritory object

I have below requirement and I'm new to SF development, need assistance if my approach is correct or not.
When any update happens on ServiceTerritory object, if the Time_Zone__c field of ServiceTerritory is not matching with User Object TimeZoneSidKey field, then update ServiceTerritory object Time_Zone__c field with User Object TimeZoneSidKey field.
ServiceTerritory object : has Center_Instructor_Contact__c field tagged to ID field in Contact object.
Contact object : has ID field and AccountId field
User Object : has AccountId field
public static void afterUpdate(List<ServiceTerritory> serviceTerritories, Map<Id, ServiceTerritory> oldRecords) {
Set<Id> recIds = new Set<Id>();
for (ServiceTerritory record : serviceTerritories) {
recIds.add(record.Id);
}
Set<Id> STMembers = new Set<Id>();
for (ServiceTerritory member : [SELECT Id, Center_Instructor_Contact__c FROM ServiceTerritory WHERE Id IN :recIds]) {
STMembers.add(member.Center_Instructor_Contact__c);
}
//Contact object : has ID field and AccountId field
Set<Id> ContactIDs = new Set<Id>();
for (Contact Cnt : [SELECT AccountId FROM Contact WHERE Id IN :STMembers]) {
ContactIDs.add(Cnt.AccountId);
}
//User Object : has AccountId field
Set<Id> UserIDs = new Set<Id>();
for (User Cnt : [SELECT AccountId, TimeZoneSidKey FROM User WHERE AccountId IN :ContactIDs]) {
UserIDs.add(Cnt.AccountId);
}
}
and here how to compare and update ServiceTerritory object if the timezone is not matching between the objects.
Not many Salesforce instances will have access to "Field Service Lightning" which is where ServiceTerritory table is used. If you sign up for free Salesforce Developer Edition it probably won't exist there. So it's bit hard to understand the question and help. Plus if you wrote "User Object : has AccountId field" it sounds like you're using Experience Cloud (formerly known as communities), that narrows down the specialists even more.
So it's Service Territory -> "up" to -> Contact -> "down" to (community) -> User?
If you're using community users they'll have AccountId and ContactId in them, no need going via Account (and in fact you could get stupid results that way... What if not all contacts in account are community-enabled, what if they are but have different timezones...)
Try something like that but you'll have to experiment a lot. And change your code to run "before update", you'll get save to database for free
List<ServiceTerritory> serviceTerritories; // passed to your function
Set<Id> contactIds = new Set<Id>();
for (ServiceTerritory st : serviceTerritories) {
contactIds.add(st.Center_Instructor_Contact__c);
}
System.debug(contactIds);
// Grab all these Contacts and their community users (it's a related list so it'll be a subquery but really there will be at most one user
Map<Id, Contact> contacts = new Map<Id, Contact>([SELECT Id,
(SELECT TimezoneSidKey FROM Users)
FROM Contact
WHERE Id IN :contactIds AND Id IN (SELECT ContactId FROM User)]);
// Loop again and check against "reference data"
for (ServiceTerritory st : serviceTerritories) {
if(contacts.containsKey(st.Center_Instructor_Contact__c)){
Contact c = contacts.get(st.Center_Instructor_Contact__c);
System.debug(c);
System.debug('comparing ' + st.Time_Zone__c + ' and ' + c.Users);
if(st.Time_Zone__c != c.Users[0].TimezoneSidKey){
System.debug('fixing ' + st.Id);
st.Time_Zone__c = c.Users[0].TimezoneSidKey;
}
}
}

INSUFFICIENT_ACCESS_OR_READONLY Error on Order Items deletion, for System Administrator profile

Even though I have a System Administrator profile assigned to me, and have the CRUD permissions on the Order Line Item Object, I am receiving the below error when I try to delete the Order Item records through DML Operation in my apex class.
System.DmlException: Delete failed. First exception on row 0 with id 8022M00000ANsKKQA1; first error: INSUFFICIENT_ACCESS_OR_READONLY, insufficient access rights on object id: []
I have written the Apex Class as "Without sharing" to avoid any conflict related to sharing rules. But still, I am receiving this error. Can anyone please let me know why this may be happening. I am calling this class from an after update trigger Also, I am inserting customer history records using the same class too. The apex class is as below.
public without sharing class LineItemHistoryRecordCreation {
public LineItemHistoryRecordCreation() {}
public void createLineItemHistoryRecord(List<OrderItem> ordItemList) {
List<Id> oliIdList = new List<Id>();
List<History__c> historyList = new List<History__c>();
for(OrderItem oli : ordItemList) {
oliIdList.add(oli.Id);
History__c his = new History__c();
his.Change_Log__c = 'Order Product record deleted-OrderNumber ' + oli.OrderId + '-' + oli.Id + '-' + oli.OB_Order_ID__c;
his.History_Type__c = 'Order Product-Deleted';
his.History_DateTime__c = DateTime.now();
his.Record_Name__c = oli.Id;
his.Prior_Value__c = 'Active';
his.New_Value__c = 'Deleted';
historyList.add(his);
}
insert historyList;
List<OrderItem> oliList = [SELECT Id FROM OrderItem WHERE Id IN :oliIdList];
System.debug('order List akki' + oliList);
if(!oliList.isEmpty()) {
delete oliList;
}
}
}
Can someone please help me with understanding and solving this. Thanks in advance.
Please refer this link
You need to set Status as Draft
https://salesforce.stackexchange.com/questions/218993/insufficient-access-or-readonly-when-deleting-orderitems-in-apex

How To Understand #TestClass Apex code with List embedded in Map

I am trying to understand this code ,this seems to be a test class,but i am having hard time to understand the code,i know conceptually how List and Map collection works in Sales force,but this seems to be little difficult to understand,
.In brief to me this seems to test a method which browses a list of CollaborationGroupMember.
For that, CollaborationGroup has been created and code tried to add one User.
can some one please take some time to make me understand the below code line by line?
Thanks in advance
#isTest
public class TestGroupFactory {
public static Map<CollaborationGroup, List<CollaborationGroupMember>> groupWithMember() {
CollaborationGroup groupe = new CollaborationGroup(Name = 'Test1', CollaborationType = 'Public');
insert groupe;
groupe = [SELECT Id, Name FROM CollaborationGroup WHERE Name = 'Test1'];
List<User> users = [SELECT Id, Name, Numero_de_plaque__c, SenderEmail
FROM User
WHERE Name = 'User User'];
List<CollaborationGroupMember> cgms = new List<CollaborationGroupMember>();
for (User u : users) {
CollaborationGroupMember cgm = new CollaborationGroupMember();
cgm.CollaborationGroupId = groupe.Id;
cgm.MemberId = u.Id;
cgms.add(cgm);
}
insert cgms;
return new Map<CollaborationGroup, List<CollaborationGroupMember>>{groupe => cgms};
}
}
It is technically a test class, but it does not perform any tests. Its purpose is to create test data for other test classes that contain test methods. The reason it has the #isTest annotation is so that it is only accessible in test context and does not count against the total test coverage of the organization.
The method shown creates a Chatter Group and adds Users to the group if they have the name "User User".
The code below inserts the Chatter Group and then retrieves it so the Id is available. I don't think the retrieval is necessary in this instance, but I'd have to test it.
CollaborationGroup groupe = new CollaborationGroup(Name = 'Test1', CollaborationType = 'Public');
insert groupe;
groupe = [SELECT Id, Name FROM CollaborationGroup WHERE Name = 'Test1'];
The next section retrieves the Users (presumably created in another test class)
List<User> users = [SELECT Id, Name, Numero_de_plaque__c, SenderEmail
FROM User
WHERE Name = 'User User'];
Then, a list of CollaborationGroupMembers is instantiated. A loop begins that iterates over every User. For each user, a new CollaborationGroupMember is instantiated and added to the list.
List<CollaborationGroupMember> cgms = new List<CollaborationGroupMember>();
for (User u : users) {
CollaborationGroupMember cgm = new CollaborationGroupMember();
cgm.CollaborationGroupId = groupe.Id;
cgm.MemberId = u.Id;
cgms.add(cgm);
}
The group members are inserted
insert cgms;
The group and group members are added to a map and returned
return new Map<CollaborationGroup, List<CollaborationGroupMember>>{groupe => cgms};

Apex Retrieve Tasks Assigned to Current User For All Contact Roles on an Account

I have an Apex class whose purpose it is to retrieve and delete overdue tasks on the contact role (related to an account) that the user just called. I need to modify it so that it queries for all overdue tasks assigned to the user on ALL contact roles on the account but am struggling to get the right query.
Here is a portion of the code in question, the part I think is most relevant:
/***************************************************
Brief Description: Deletes overdue Tasks or Events.
****************************************************/
public class DSDenali_DeleteOverDueActivities {
private static List<Task> followUpTasksToDelete = new List<Task>();
private static List<Event> followUpEventsToDelete = new List<Event>();
private static Map<Id, Set<String>> ownerOfActivities = new Map<Id, Set<String>>();
#InvocableMethod (label = 'DialSource Denali Delete Overdue Activities')
public static void gatherData(List<Data> requests)
{
Map<Id, String> results = new Map<Id, String>();
for (Data request : requests)
results.put(request.contactRoleID, request.assignedTo);
for (Id key : results.keySet())
{
Set<String> assignedToValues = parseAssignedTo(results.get(key));
System.debug('assignedToValues: ' + assignedToValues);
ownerOfActivities.put(key, assignedToValues);
System.debug(ownerOfActivities);
}
queryAndFilterData();
deleteOverdueActivities();
}
//Query for the Tasks and Events and filter the ones to delete
private static void queryAndFilterData()
{
List<Contact_Role__c> contactRoles = [SELECT Id,
(SELECT Id, Owner.UserRole.Name, OwnerId FROM Tasks WHERE status != 'Completed' AND ActivityDate <= :system.TODAY() AND Type__c = 'Outbound Call'),
(SELECT Id, Owner.UserRole.Name, OwnerId, Description FROM Events WHERE EndDateTime <= :system.NOW())
FROM Contact_Role__c
WHERE Id IN :ownerOfActivities.keySet()];
for (Contact_Role__c contactRole : contactRoles)
{
for (Task currentTask : contactRole.Tasks)
{
if (ownerOfActivities.get(contactRole.Id).contains(currentTask.OwnerId))
{
if (currentTask.OwnerId != '0050B000006ET37' && currentTask.Owner.UserRole != NULL && Pattern.matches('.*Altair.*', currentTask.Owner.UserRole.Name))
followUpTasksToDelete.add(currentTask);
else if (currentTask.OwnerId == '0050B000006ET37')
followUpTasksToDelete.add(currentTask);
else
continue;
}
else if (ownerOfActivities.get(contactRole.Id).contains('ALL'))
{
if (currentTask.Owner.UserRole != NULL && Pattern.matches('.*Altair.*', currentTask.Owner.UserRole.Name))
followUpTasksToDelete.add(currentTask);
else
continue;
}
}
for (Event currentEvent : contactRole.Events)
{
if (ownerOfActivities.get(contactRole.Id).contains(currentEvent.OwnerId) && currentEvent.Description == NULL)
{
if (currentEvent.OwnerId != '0050B000006ET37' && currentEvent.Owner.UserRole != NULL && Pattern.matches('.*Altair.*', currentEvent.Owner.UserRole.Name))
followUpEventsToDelete.add(currentEvent);
else if (currentEvent.OwnerId == '0050B000006ET37')
followUpEventsToDelete.add(currentEvent);
else
continue;
}
else if (ownerOfActivities.get(contactRole.Id).contains('ALL') && currentEvent.Description == NULL)
{
if (currentEvent.Owner.UserRole != NULL && Pattern.matches('.*Altair.*', currentEvent.Owner.UserRole.Name))
followUpEventsToDelete.add(currentEvent);
else
continue;
}
}
}
}
//Delete overdue Events/Tasks
private static void deleteOverdueActivities()
{
try{
delete followUpTasksToDelete;
}
catch (DmlException e){
System.debug('The following error occured (DSDenali_DeleteOverDueActivities): ' + e);
}
try{
delete followUpEventsToDelete;
}
catch (DmlException e){
System.debug('The following error occured (DSDenali_DeleteOverDueActivities): ' + e);
}
}
//Parse the CSVs of possible owners
private static Set<String> parseAssignedTo(String assignedTo)
{
Set<String> assignedToValues = new Set<String>();
assignedToValues.addAll(assignedTo.deleteWhitespace().split(','));
return assignedToValues;
}
public class Data
{
#InvocableVariable (required=true)
public String assignedTo;
#InvocableVariable (required=false)
public Id contactRoleID;
}
}
(updated after OP posted more code and asked for code review)
It's not bad code, could use some comments. Consider posting it in https://codereview.stackexchange.com/ (although not many SF-related posts end up there) or on https://salesforce.stackexchange.com
gatherData()
Your input variable (after some parsing) is Map<Id, Set<String>> where key is contact role's id. That set of strings for users (owners) is bit misleading. At a glance you immediately ask yourself why it can't be Set<Id>. Only deep down in the code you see that apparently "All" is one of allowed values. This is... not great. I'd be tempted to make 2 methods here, one taking legit Map<Id, Set<Id>> and other taking simply Set<Id> if you know that you're effectively skipping the second parameter.
queryAndFilterData()
You have only one query and it's not in a loop, very good. My trick (from before edit) won't work for you (you don't really have the Account__c or however is the field called anywere in the input, you have only record ids. If you want to check / delete all tasks for roles under this account cleanest might be to use two queries
// 1st the helper to collect all accounts...
Set<Id> allAccounts = new Map<Id, Account>([SELECT Id
FROM Account
WHERE Id IN (SELECT Account__c FROM Contact_Role__c WHERE Id IN :ownerOfActivities.keyset()]).keyset();
// then the outline of the main query would be bit like this
SELECT Id,
(SELECT ... FROM Tasks WHERE ...),
(SELECT ... FROM Events WHERE ...)
FROM Contact_Role__c
WHERE Account__c IN :allAccounts
AND ...
I'd check how much of this filtering logic could be pushed to the query itself rather than manually inspecting each returned row. I mean look at that:
Imagine we're going the simple route (ignoring concept of "All" users) and let's say you have another Set<Id> allUsers; variable (composed out of all ids mentioned in all your data pieces)
The query for Tasks could become something as simple as
(SELECT Id
FROM Tasks
WHERE Status != 'Completed'
AND ActivityDate <= TODAY
AND Type__c = 'Outbound Call'
AND OwnerId IN :allUsers
AND (OwnerId = '0050B000006ET37' OR Owner.UserRole.Name LIKE '%.Altair.%')
)
You still have to loop through them to verify whether each can really really be deleted (it's not enough to match out of all users, it has to check also if it's OK for user on this particular Contact_Role__c, right?) but something like that should return less rows and no more regular expression matching... should be bit faster.
I wouldn't have a magic variable for that one special owner's id like that. Ideally there would be something else that describes this special user (role? profile? custom field on User record? permission to "Author Apex" in profile?). At the very least move it to helper Id variable at top of the file so it's not copy-pasted all over the place. And ask your business user what should happen if that guy (default task owner? some integration account?) ever leaves the company because poo poo will hit the propeller hard.
If you'll get comfortable with this version of the query the "ALL" version would become even simpler? No "all users", no "magic id" & job done?
(SELECT Id
FROM Tasks
WHERE Status != 'Completed'
AND ActivityDate <= TODAY
AND Type__c = 'Outbound Call'
Owner.UserRole.Name LIKE '%.Altair.%'
)
Don't trust random guy on internet who doesn't know your business process but yeah, there's some room for improvement. Just test thoroughly :)
deleteOverdueActivities()
This is not great try-catch pattern. You just raise it in debug log but silently swallow the error. Make it fail hard (let the error bubble up to user) or do some proper error handling like inserting something to helper Log__c object maybe or sending an email / chatter post to admin...
parseAssignedTo()
Not bad. I expect it explodes horribly when you pass a null to it. You're kind of protected from it by marking the variable required in last lines but I think this annotation applies only to Flows. Calling it from other Apex code isn't protected enough.

Converting Trigger to Batch Apex: Help needed to make the code work

If possible, could anyone please direct me towards the right direction? The second code (Batch Apex) is just not compiling. Currently the error is,
Error: Compile Error: Invalid type: updateContactOnEmailOptOutChangeScheduler
at line 63 column 73
But I think there are other issues to that I can't seem to make it right.
On a contact update, the trigger updates all duplicate contacts with new value of "Email Opt Out" if this field has been updated. Also, the trigger only updates duplicate contacts that have a HasOptedOutOfEmail value different from one being updated. Now my task is to convert this requirement from trigger (that was written and tested by my colleague) to Batch Apex. First is the original trigger. Second is the code I just wrote in the format of batch apex.
Original Trigger Code
trigger updateContactOnEmailOptOutChange on Contac​t (after update) { ​ ​
//Initialize lists and maps
List<Contact> duplicateContacts = new List<Con​tact>();
Map<String, Contact> contactEmailMap = new Map​<String, Contact>();
Map<Id, Contact> contactIdMap = new Map<Id, Co​ntact>();
//Build a map with contacts to update. Only se​lect the ones that have a different "Email Opt Out​" value from the contact being updated.
for (Integer i = 0; i < Trigger.new.size(); i+​+) {
if (Trigger.old[i].HasOptedOutOfEmail != T​rigger.new[i].HasOptedOutOfEmail) {
contactEmailMap.put(Trigger.old[i].ema​il, Trigger.new[i]);
contactIdMap.put(Trigger.old[i].id, Tr​igger.new[i]);
}
}
//Only go through this process if "Email Opt O​ut" (HasOptedOutofEmail) was updated.
If (contactIdMap.size()>0) {
//Query the database and look for all cont​acts with a duplicate email address (same email as​ the contact currently being updated).
for (Contact dupContact : [SELECT Id, Name​, Email, HasOptedOutOfEmail
FROM Contact
WHERE Email IN ​: contactEmailMap.KeySet()
AND Id NOT IN :​ contactIdMap.KeySet()]) {
Contact contact = contactEmailMap.get(​dupContact.Email);
If (dupContact.HasOptedOutOfEmail <> c​ontact.HasOptedOutOfEmail) {
dupContact.HasOptedOutOfEmail = co​ntact.HasOptedOutOfEmail;
duplicateContacts.add(dupContact);
}
}
//If any duplicate contacts were found, up​date all duplicate contacts with the new HasOptedO​utOfEmail value.
If (duplicateContacts.size()>0) update dupl​icateContacts;
}
}
BATCH APEX
global class updateContactOnEmailOptOutChange implements Database.Batchable<sObject>
{
global string query;
global updateContactOnEmailOptOutChange()
{
query = 'SELECT id,Name, Email, HasOptedOutofEmail from Contact where HasOptedOutofEmail=true';
}
global Database.QueryLocator start(Database.BatchableContext BC)
{
return Database.getQueryLocator(query);
}
global void execute(Database.BatchableContext BC, List <sObject> duplicateContacts)
{
Map<String, Contact> contactEmailMap = new Map<String, Contact>();
Map <Id, Contact> contactIdMap = new Map<Id, Contact>();
// Build a map with contacts to update. Only select the ones that have a different "Email Opt Out" value from the contact being updated.
if(trigger.isUpdate){
for(Integer i=0; i<Trigger.new.size();i++)
{
if(Trigger.old[i].HasOptedOutOfEmail != Trigger.new[i].HasOptedOutOfEmail)
{
contactEmailMap.put(Trigger.old[i].email, Trigger.new[i]);
contactIdMap.put(Trigger.old[i].id, Trigger.new[i]);
}
}
if(contactidMap.size()>0)
{
//Query the database and look for all contacts with a duplicate email address(same email as the contact currently being updated)
for (Contact dupContact: [SELECT Id, Name, Email, HasOptedOutofEmail
FROM Contact
WHERE Email IN: contactEmailMap.KeySet()
AND Id NOT IN: contactIdMap.KeySet()])
{
Contact contact=contactEmailMap.get(dupContact.Email);
If(dupContact.HasOptedOutOfEmail <> contact.HasOptedOutOfEmail)
{
dupContact.HasOptedOutOfEmail = contact.HasOptedOutOfEmail;
duplicateContacts.add(dupContact);
}
}
// if any duplicate contacts were found, update all duplicate contacts with the new HasOptedOutOFEmail value.
If(duplicateContacts.size<>0) update duplicateContacts;
}
}
}
//The batch process has completed successfully. Schedule next batch.
global void finish(Database.BatchableContext BC){
// //Build the system time of now + 300 seconds to schedule the batch apex.
Datetime sysTime = System.now();
sysTime = sysTime.addSeconds(300);
String chron_exp=''+sysTime.second()+''+sysTime.minute()+​''+sysTime.hour()+''+sysTime.day()+''+sysTime.mont​h()+'?'+sysTime.year();
system.debug(chron_exp);
updateContactOnEmailOptOutChangeScheduler scheduleFieldUpdate = new updateContactOnEmailOptOutChangeScheduler();
//Schedule the next job, and give it the system time so name is unique
System.schedule('New Email Update Job'+sysTime.getTime(),chron_exp,scheduleFieldUpda​te);
}
}
Your batch apex is including references to the Trigger class that is only valid inside a trigger. The compile error message says there is an invalid type on line 63. Check the line numbers but that likely points to the reference to Trigger

Resources