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
Sourav PSourav P 

To Count the records remaining/not used for a particular within the object

Hi,
I have a custom object, with one checkbox field as "Record Used". I want to know that if the remaining Record Used with checkbox not ticked ( false) become less than or equal to 100, it should send us the email alert, so that we can add more records to be used .
I am trying to create a formula field for this but i think its not going to work, can anyone plz suggest me a solution for this ? its within the same object and there is no cross object involved.Thnx
,
Best Answer chosen by Sourav P
Nayana KNayana K
@istest
class TestObjScheduledClass 
{

   static testmethod void unitTest() 
   {
	   String CRON_EXP = '0 0 0 3 9 ? 2022';
	   
	   Test.startTest();
		
		/* Setup Test data - START */
		List<Obj__c> lstObj = new List<Obj__c>();
		for(Integer i=0; i<50; i++)
		{
			lstObj.add(new Obj__c(Name = 'Test'+i, Record_Used__c = FALSE));
		}
		insert lstObj;
		/* Setup Test data - END */

      // Schedule the test job

      String jobId = System.schedule('testBasicScheduledApex', CRON_EXP, new ObjScheduledClass());

      // Get the information from the CronTrigger API object
      CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, 
         NextFireTime
         FROM CronTrigger WHERE id = :jobId];

      // Verify the expressions are the same
      System.assertEquals(CRON_EXP, 
         ct.CronExpression);

      // Verify the job has not run
      System.assertEquals(0, ct.TimesTriggered);

      // Verify the next time the job will run
      System.assertEquals('2022-09-03 00:00:00', 
         String.valueOf(ct.NextFireTime));
      
		Integer invocations = Limits.getEmailInvocations();
		Test.stopTest();
		// verify if email sent
		System.assertEquals(1, invocations);
   

   }
}

Try this (changed Test.stopTest position)

All Answers

Nayana KNayana K
If you go for trigger,

trigger ObjTrigger on Obj__c (after insert, after update)
{
    Integer intCnt = [SELECT COUNT()
            FROM Obj__c
            WHERE Not_Used_c = FALSE];
    if(intCnt <= 100)
    {
        // send email logic here
    }
}

something like this can do.
Everytime you insert a record with not used, you will recieve email until count reaches 101
Sourav PSourav P
Thanks Nayana, Let me try this.
M SM S
@nayana - I feel the logic will email the users everytime the records will be updated for any reason. Hence, it can flood the inbox for the end user for any value update. Rather use the concept of Rollup summary if your object model allows you to do so.

Thanks
MS
Nayana KNayana K
That's true. Inbox will flood with email for update, object model doesn't have any cross reference field. 
Below code can be usefull in update case.
 
trigger ObjTrigger on Obj__c (after insert, after update)
{
	Boolean blnChanged = false;
	for(Obj__c objC : Trigger.New)
	{
		if(objC.Not__Used__c = FALSE && (Trigger.isInsert || (Trigger.isUpdate && objC.Not_Used__c != Trigger.oldMap.get(objC.Id).Not_Used__c))
		{
			blnChanged = true;
			break;
		}
	}

	if(blnChanged)
	{
	    Integer intCnt = [SELECT COUNT()
	            FROM Obj__c
	            WHERE Not_Used_c = FALSE];
	    if(intCnt <= 100)
	    {
	        // send email logic here
	    }
	}
}

 
Sourav PSourav P
Hi Nayana,
i was trying to use the above code, But its showing some issue , can you plz check and suggest where i went wrong ?
Also, I need to do the below , which i am not able to accommodate in the right place, thanks
1. I want to use four emails to be send.
2. The emails to be sent everyday till the remaining not used records are 500 or less
 
trigger ObjTrigger on Obj__c(after insert, after update)
{
    Boolean blnChanged = false;
    for(Obj__c objC : Trigger.New)
    {
        if(objC.Not__Used__c = FALSE && (Trigger.isInsert || (Trigger.isUpdate && objC.Not__Used__c != Trigger.oldMap.get(objC.Id).Not__Used__c)))
        {
            blnChanged = true;
            break;
        }
    }

    if(blnChanged)
    {
Integer intCnt = [SELECT COUNT() FROM Obj__c WHERE Not__Used__c = FALSE];
if(intCnt <= 500)
{
// send email logic 
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();  
string body = 'Hi ';
String[] toAddresses = new String[] {'sourav@xxxx.com'}; // I want to use four emails, can i use by comma here ? I tried but it didn't worked. When i used the same logic multiple times with one email each, It showed "toAddresses" variables been used multiple times
mail.setToAddresses(toAddresses);
mail.setSubject('Remaining Compulsory Barcode is less than 500');  
mail.setSaveAsActivity(false);  
for(OrgWideEmailAddress owa : [select id, Address, DisplayName from OrgWideEmailAddress]) 
{
 if(owa.Address.contains('service')) 
  mail.setOrgWideEmailAddressId(owa.id); 
}
mail.setHtmlBody('The remaining not used records are less than 500' );  // This will be my email body , its correct ?
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}
}



 
Sourav PSourav P
Hi Nayana, The above code seems OK now, but it seems it will send emails everytime the " Not used" field will change, But what i need is it should get trigger once per day, Can you plz suggest if add any criteria for it? Thanks
Nayana KNayana K
If you don't want update use case,  then remove that part of code.  
But on insert case,  if you insert 5 records one after other then will send email.... 

Better go for schedule apex.. Scheduling it every day midnight say 11.45 ....So per day only once email will send  
Sourav PSourav P
Hi Nayana , Thanks. As i am new to apex, your helps & suggestions are very helpful.
I tried to write a schedule apex, Can you plz check if its OK ? How to schedule the time , Is that i have to write within the code or it will happen automatically by the midnight everyday ?
 
global class ObjScheduledClass Implements Schedulable
{
global void execute(SchedulableContext sc)
{
integer cnt=[SELECT COUNT() FROM Obj__c WHERE Record_Used__c = FALSE];
If(cnt<=500)
{
sendmail();
}
}
public void sendmail()
{
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();  
string body = 'Hi ';
String[] toAddresses = new String[] {'sourav@xxxx.com','sn@xxxx.com'}; 
mail.setToAddresses(toAddresses);
mail.setSubject('Remaining Record is less than 500');  
mail.setSaveAsActivity(false);  
for(OrgWideEmailAddress owa : [select id, Address, DisplayName from OrgWideEmailAddress]) 
{
 if(owa.Address.contains('service')) 
  mail.setOrgWideEmailAddressId(owa.id); 
}
mail.setHtmlBody('The remaining record is less than 500, Take action' );  
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}

 
Sourav PSourav P
also, Is there a way i can test it now, or haave to wait for the scheduled time ? ( How can i set the time of run per day ?) Thanks
Nayana KNayana K
Setup -> Develop -> Classes

You will see a "Schedule Apex" button. You can set up the timing from there.

Something like run everyday at this particular time... Yes you have to wait... 


For immediate testing,  try setting 2mins after current time if possible.... Then check if you get email after time crosses..

 
Sourav PSourav P
Thnx, Set the timings, Just waiting for the emails to trigger if my code is correct.
Nayana KNayana K
- We can schedule it via code using system.schedule() method in Dev console anonymous window. For that,  you have to understand  cron expressions. Please go through and explore resources on the same. 
- Your code looks fine to me.  Please don't forget to comment out / delete trigger since the approach is now works from scheduled apex. 
- Scheduling the class end of the day some where around 11.55 or something makes sense because you are collectively querying the records when day is almost ends and sending one email based on criteria which means next day morning the email receiver can take action accordingly. 

I apologize if you feel I am going out of context. 
Sourav PSourav P
Hi Nayana, I just wrote the below test code , But the code coveraage is not satisfying, its coming exactly 75%, I need to increase the coverage, Can you plz help how can i do that.
 
@isTest
public class TestObjScheduledClass {
public static testMethod void unitTest(){
Test.startTest();
ObjScheduledClass cbs=new ObjScheduledClass();
cbs.Sendmail();
Test.stopTest();
}
}

 
Nayana KNayana K
http://blog.shivanathd.com/2013/11/Best-Practices-Test-Class-in-Salesforce.html

Below link has explanation for writing test class related to Scheduled apex:
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_scheduler.htm
 
@istest
class TestObjScheduledClass 
{

   static testmethod void unitTest() 
   {
	   String CRON_EXP = '0 0 0 3 9 ? 2022';
	   
	   Test.startTest();
		
		/* Setup Test data - START */
		List<Obj__c> lstObj = new List<Obj__c>();
		for(Integer i=0; i<50; i++)
		{
			lstObj.add(new Obj__c(Name = 'Test'+i, Record_Used__c = FALSE));
		}
		insert lstObj;
		/* Setup Test data - END */

      // Schedule the test job

      String jobId = System.schedule('testBasicScheduledApex', CRON_EXP, new ObjScheduledClass());

      // Get the information from the CronTrigger API object
      CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, 
         NextFireTime
         FROM CronTrigger WHERE id = :jobId];

      // Verify the expressions are the same
      System.assertEquals(CRON_EXP, 
         ct.CronExpression);

      // Verify the job has not run
      System.assertEquals(0, ct.TimesTriggered);

      // Verify the next time the job will run
      System.assertEquals('2022-09-03 00:00:00', 
         String.valueOf(ct.NextFireTime));
      
		Integer invocations = Limits.getEmailInvocations();
		
		// verify if email sent
		System.assertEquals(1, invocations);
   Test.stopTest();

   }
}

 
Sourav PSourav P
Thanks a lot Nayana. I am going through all the docs to expand my knowledge on this :).
For the above, in line
lstObj.add(new Obj__c(Name = 'Test'+i, Record_Used__c = FALSE));
Its showing ,
Error: Compile Error: Field is not writeable: Obj__c.Name at line 15 column 13
The Name type is an auto-number. We need to change somthing here ? 
Nayana KNayana K
Change it to 
lstObj.add(new Obj__c(Record_Used__c = FALSE));
Nayana KNayana K
Hey did it worked? 
Sourav PSourav P
Helllo Nayana, It saved properly now. But unfortunately, now my code coveraage is shwoing 0%, If we need to change somwhere ? thanks

User-added image

Is that we need to take the 2022 below ? or its OK.?

// Verify the next time the job will run
      System.assertEquals('2022-09-03 00:00:00',
         String.valueOf(ct.NextFireTime));
Nayana KNayana K
What failure message are you getting?
Sourav PSourav P
In Logs its showing status as " Assertion failed, Expected : 1, Actual : 0".
If this status is OK, or if you need some more info plz ?
Nayana KNayana K
@istest
class TestObjScheduledClass 
{

   static testmethod void unitTest() 
   {
	   String CRON_EXP = '0 0 0 3 9 ? 2022';
	   
	   Test.startTest();
		
		/* Setup Test data - START */
		List<Obj__c> lstObj = new List<Obj__c>();
		for(Integer i=0; i<50; i++)
		{
			lstObj.add(new Obj__c(Name = 'Test'+i, Record_Used__c = FALSE));
		}
		insert lstObj;
		/* Setup Test data - END */

      // Schedule the test job

      String jobId = System.schedule('testBasicScheduledApex', CRON_EXP, new ObjScheduledClass());

      // Get the information from the CronTrigger API object
      CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, 
         NextFireTime
         FROM CronTrigger WHERE id = :jobId];

      // Verify the expressions are the same
      System.assertEquals(CRON_EXP, 
         ct.CronExpression);

      // Verify the job has not run
      System.assertEquals(0, ct.TimesTriggered);

      // Verify the next time the job will run
      System.assertEquals('2022-09-03 00:00:00', 
         String.valueOf(ct.NextFireTime));
      Test.stopTest();
		Integer invocations = Limits.getEmailInvocations();
		
		// verify if email sent
		System.assertEquals(1, invocations);
   

   }
}

 
Nayana KNayana K
In above code, I have chnaged Test.stopTest() position because, after Test.stopTest(), the execute() will actually run and email sending code will cover.

This is another way we can write scheduled apex test class.
http://www.infallibletechie.com/2013/12/test-class-for-schedulable-class-in.html

 
Nayana KNayana K
I think 
1. if(cnt <= 500)

has to be changed to
2. if(cnt >0 && cnt <= 500)

Because in #1, even if there is no record with No_Used = false (0 count), email will be sent. Ideally, if atleast one record is there then email should be sent I think (#2)
Sourav PSourav P
Hi Nayana, Thank you, seems good logic, Let me try it :)
Sourav PSourav P
I tried to change it in the apex class, but till the status showing the same & zero coverage :(.
May i know plz why we have kept this, I dont see any use of it, if it will make any difference ? Thnx
for(Integer i=0; i<50; i++)
Nayana KNayana K
Not sure why it is not covering.

for(Integer i=0; i<50; i++) // just to insert 50 records in bulk (which satisfies cnt <=500 condition)

Still you have error?

 
Sourav PSourav P
Hi Nayana, Yes, till :(.
Seems its failing to run the test code as below, Here , i have posted the screenshots of the errors, plz suggest, Thanks

User-added image

User-added image

User-added image

The below is line 40, if some issue here ?
System.assertEquals(1, invocations);


 
Nayana KNayana K
System.assertEquals(1, invocations); // if send email has not fired the you will get 0. Which means code is not covered...Not sure what I am missing.

If even if you remove this line, you will get 0% I think.
Nayana KNayana K
Just to cross verify:
Did you changed anything in the code?
Can you paste both class and test class present in your org here once?
Nayana KNayana K
@istest
class TestObjScheduledClass 
{

   static testmethod void unitTest() 
   {
	   String CRON_EXP = '0 0 0 3 9 ? 2022';
	   
	   Test.startTest();
		
		/* Setup Test data - START */
		List<Obj__c> lstObj = new List<Obj__c>();
		for(Integer i=0; i<50; i++)
		{
			lstObj.add(new Obj__c(Name = 'Test'+i, Record_Used__c = FALSE));
		}
		insert lstObj;
		/* Setup Test data - END */

      // Schedule the test job

      String jobId = System.schedule('testBasicScheduledApex', CRON_EXP, new ObjScheduledClass());

      // Get the information from the CronTrigger API object
      CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, 
         NextFireTime
         FROM CronTrigger WHERE id = :jobId];

      // Verify the expressions are the same
      System.assertEquals(CRON_EXP, 
         ct.CronExpression);

      // Verify the job has not run
      System.assertEquals(0, ct.TimesTriggered);

      // Verify the next time the job will run
      System.assertEquals('2022-09-03 00:00:00', 
         String.valueOf(ct.NextFireTime));
      
		Integer invocations = Limits.getEmailInvocations();
		Test.stopTest();
		// verify if email sent
		System.assertEquals(1, invocations);
   

   }
}

Try this (changed Test.stopTest position)
This was selected as the best answer
Sourav PSourav P
And here it goes ,finally i got the 100% code coverage from 0% :). Thank a lots Nayana for your efforts and help.
Nayana KNayana K
Most welcome :)