After Update in Apex Class - Total Contacts "Roll-Up Summary" - salesforce

I added the Apex Class and Apex trigger below to update the field Number_of_Contacts at the Account level when a Contact is added or removed to a certain Account.
My idea is to display in Accounts reports, how many Contacts an Account has. I had to do this, because Salesforce doesn't provide a Roll-Up Summary, at the Account level, to count Contacts.
I also tried creating a Flow, but it only works when a Contact is created or deleted.
Here are Apex Class and Trigger I tried to use:
Class:
public without sharing class ContactTriggerHandler {
private Set<Id> getAccountIds(List<Contact> contacts) {
Set<Id> accountIds = new Set<Id>();
for(Contact c : contacts) {
if(c.AccountId != null) {
accountIds.add(c.AccountId);
}
}
return accountIds;
}
private Map<Id, Account> getAccountMap(Set<Id> accountIds) {
return new Map<Id, Account>([SELECT Id, Number_of_Contacts__c FROM Account WHERE Id in :accountIds]);
}
public void process(List<Contact> contacts, System.TriggerOperation operation) {
Set<Id> accountIds = getAccountIds(contacts);
if(accountIds.size() > 0) {
Map<Id, Account> accountMap = getAccountMap(accountIds);
for(Contact c : contacts) {
if(accountMap.containsKey(c.AccountId)) {
switch on operation{
when AFTER_INSERT {
accountMap.get(c.AccountId).Number_of_Contacts__c += 1;
}
when AFTER_DELETE {
accountMap.get(c.AccountId).Number_of_Contacts__c -= 1;
}
when AFTER_UNDELETE {
accountMap.get(c.AccountId).Number_of_Contacts__c += 1;
}
}
}
}
update accountMap.values();
}
}
}
Trigger
trigger ContactTrigger on Contact (after insert, after delete, after undelete) {
ContactTriggerHandler handler = new ContactTriggerHandler();
switch on Trigger.operationType {
when AFTER_INSERT {
handler.process(Trigger.new, Trigger.operationType);
}
when AFTER_DELETE {
handler.process(Trigger.old, Trigger.operationType);
}
when AFTER_UNDELETE {
handler.process(Trigger.new, Trigger.operationType);
}
}
}
However, how can I include a line of code that updates the Number_of_Contacs__c field when a Contact moves to a different Account (like, an "After_Update" trigger)?
Thank you,
I tried some guidance on how to add AFTER UPDATE triggers in Apex Code, but I didn't succeed.

Trigger context variables give you access to old and new values, state of the database before the user clicked save and what the user has changed. You're passing from trigger to class only trigger.new but to get the id of original account the contact was linked to and update it - something like this.
Set<Id> idsToCheck = new Set<Id>();
// During update, only if account id changes...
for(Contact c : trigger.new){
Contact old = trigger.oldMap.get(c.Id);
if(c.AccountId != old.AccountId){
idsToCheck.add(c.AccountId);
idsToCheck.add(old.AccountId); // mark both for further processing
}
}
idsToCheck.remove(null);
List<Account> accounts = [SELECT Id, (SELECT Id FROM Contacts) FROM Account WHERE Id IN :idsToCheck];
for(Account a : accounts){
a.Number_of_Contacts__c = a.Contacts.size();
}
update accounts;

Related

Test class for Apex Trigger for update

I am new in Apex Development. I want to write a TestClass for my Apex Trigger.
I am sharing my code with you:
trigger ClosedOpportunityTrigger on Opportunity (before update) {
for(Opportunity o:Trigger.New) {
if(o.Probability==100 && o.Invoiced__c == true)
{
Attachment a = new Attachment();
try {
a = [Select Id, Name from Attachment where ParentId =:o.Id];
}
catch(Exception e) {
a = null;
}
if (a == null)
o.addError('Add an attachment before you close the Opportunity');
}
}
}
There are 2 things that need to be done :-
1. Create a record in 'Opportunity' object. Update it using code.
2. Create an attachment. Here is the link for your reference https://developer.salesforce.com/forums/?id=906F00000008yzKIAQ
Goodluck
public static void testmethod(){
opportunity opp = new opportunity()
//change whatever fields u need to make probability 100%
opp.stage = 'Closed Won';
opp.Invoiced__c == true;
try{
insert opp
}
catch(Exception e){
string errormessage = e.getMessage();
}
//now that u know how to do it, do a positive test where you also add an
//attachment
//as a side note, your catch block will never fire, because you aren't
//catching any exceptions you are just getting a null string
}

how to fix System.NullPointerException: Attempt to de-reference a null object in apex class using Map<parentId, child>?

Getting the error in line " taskObj.OwnerId = ConMap.get(ben.contact__c).contact__r.OwnerId;" becasue the ownerid field is on contact.
Contact is the parent of benefit, Here I am getting all the benefits in start method. I want to add contactid only once if it has more than one child for that I used SET. I want to use maps as I need to get the contact OwnerId field from contact object which I am fetching in the query in start method. How do I Access contact.ownerId field using a map? below is the code.
global Database.QueryLocator start(Database.BatchableContext bc) {
Query='select contact__r.ownerId, contact__c, Task_creation_date__c, Status__c, task_created__c, type__c from Benefit__c Where Task_creation_date__c= TODAY AND Status__c IN (\'Active\',\'Pending\') AND Task_created__c =FALSE AND Type__c!=\'Short Term Medical\'';
return Database.getQueryLocator(query);
}
global void execute(Database.BatchableContext bc, List<Benefit__c> scope){
// process each batch of records
List<Contact> contacts = new List<Contact>();
List<Task> TaskList = new List<Task>();
set<id> conset = New set<id>();
Map<id,benefit__c> ConMap = new map<id,Benefit__c>();
for (Benefit__c b : scope) {
conset.add(b.contact__c);
ConMap.put(b.contact__c, b);
b.task_created__c = TRUE;
}
system.debug('contact and its benefit------'+ConMap);
recordsProcessed = conset.size();
//List<Contact> tempList = new List<Contact>();
// tempList = [Select Id,OwnerId, firstname from Contact where Id IN:(conset)];
if(!ConMap.isEmpty())
{
for(Benefit__c ben : ConMap.values())
{
Task taskObj = new Task();
taskObj.OwnerId = ConMap.get(ben.contact__c).contact__r.OwnerId;
I want to populate contact ownerid as the task ownerid but how do I access it from the map and keep the unique contact id in the map?
I see that the batch query does not have the filtering condition 'Contact__c != null'. So, it's possible that one of the benefit records is missing value in the 'Contact__c' field and you wouldn't find it in the map. You can solve this in two ways:
Add 'Contact__c != null' to the selector query if you don't care about those records.
(Or)
Check for 'null' value in the for loop as below:
if(!ConMap.isEmpty())
{
for(Benefit__c ben : ConMap.values())
{
if(String.isBlank(ben.Contact__c)){
/* continue;
or
throw exception()
*/
}
Task taskObj = new Task();
taskObj.OwnerId = ConMap.get(ben.contact__c).contact__r.OwnerId;
I re wrote the code to resolve this nd optimize the code as below.
public with sharing class MyBatch implements Database.Batchable<AggregateResult>{
public Iterable<AggregateResult> start(Database.BatchableContext context)
{
return [
SELECT Contact__c, Contact__r.OwnerId owner FROM Benefit__c
WHERE Contact__c != null AND ...
GROUP BY Contact__c, Contact__r.OwnerId
];
}
public void execute(Database.BatchableContext context, List<AggregateResult> scope)
{
Set<Id> contactIds = new Set<Id>();
List<Task> tasks = new List<Task>();
for (AggregateResult aggregate : scope)
{
Id ownerId = (Id)aggregate.get('owner');
Id contactId = (Id)aggregate.get('Contact__c');
contactIds.add(contactId);
tasks.add(new Task(OwnerId=ownerId, /*other fields*/));
}
insert tasks;
List<Benefit__c> benefits = [
SELECT Id FROM Benefit__c WHERE Contact__c IN :contactIds
];
for (Benefit__c benefit : benefits)
benefit.Task_Created__c = true;
update benefits;
}
public void finish(Database.BatchableContext context) { /*additional logic*/ }}

To get the number of opportunities in an Account

how to count number of opportunities related to account,
total number of opportunities field on account should be increment/decrement when Opportunity is created/deleted.
How to solve it, pl help me with sample code.
Actually, you don't need write a code if you need count of all opportunities related to an account. Create a “Rollup/Summary” field type on the Account. Evaluate the Opportunity object, and run a “Count” operation. That’s it!
UPD:
If you need to solve it with trigger it will looks something like this:
trigger CountOpportunitiesOnAccount on Opportunity (after insert, after delete){
Set<Id> aId = new Set<Id>();
if(Trigger.isInsert || Trigger.isDelete || Trigger.isUndelete){
for(Opportunity opp : Trigger.New){
aId.add(opp.AccountId);
}
updateAccounts(aId);
}
if(Trigger.isDelete){
for(Opportunity opp : Trigger.old){
aId.add(opp.AccountId);
}
updateAccounts(aId);
}
private void updateAccounts(Set<Id> accIds){
List<Account> accs = [select id, OpportunitiesAmount from Account where Id in :accIds];
List<Opportunity> opps = [select id from Opportunity where AccountId in :accIds];
for(Account a : accs){
a.OpportunitiesAmount = opps.size();
}
update accs;
}
}
So, here you go. This is the Exact code that counts the Exact number of Related Opportunities of an Account and Populates it on the Standard field of Account Number ( you can add it on the custom field also).
trigger TriggTask on Opportunity (after insert, after delete)
{
List<id> TriggerList = new List<id>();
if(Trigger.isInsert)
{
List<id> TriggerList = new List<id>();
for(Opportunity Opp : Trigger.new)
{
TriggerList.add(Opp.AccountId);
}
List<Account> DML = New List<Account>();
List<Account> Ls = [select id,(select id from Opportunities) from Account where id in:TriggerList];
for(Account AccNew : ls)
{
Integer Num = AccNew.Opportunities.size();
AccNew.AccountNumber=String.valueOf(Num);
DML.add(AccNew);
}
update ls;
}
if(Trigger.isDelete)
{
for(Opportunity Opp : Trigger.old)
{
TriggerList.add(Opp.AccountId);
}
List<Account> DML = New List<Account>();
List<Account> Ls = [select id,(select id from Opportunities) from Account where id in:TriggerList];
for(Account act : Ls)
{
act.AccountNumber=String.valueOf(act.Opportunities.size());
DML.add(act);
}
update Ls;
}
}
If you must write a trigger, you might also consider the re-parenting of an Opportunity. This is why the Roll-Up Summary field is preferred by many developers. There may be edge cases that generate errors or unexpected results.
trigger CountOpportunitiesOnAccount on Opportunity (after insert, after delete, after update){
Set<Id> aId = new Set<Id>();
if(Trigger.isInsert || Trigger.isDelete || Trigger.isUndelete || Trigger.isUpdate){
for(Opportunity opp : Trigger.New){
if(Trigger.isUpdate){
if(Trigger.newMap.get(opp.Id).AccountID != Trigger.oldMap.get(opp.Id).AccountID){
aId.add(opp.AccountId);
}
}
else{
aId.add(opp.AccountId);
}
}
if(aID != NULL && aID.size() > 0){
updateAccounts(aId);
}
}
if(Trigger.isDelete){
for(Opportunity opp : Trigger.old){
aId.add(opp.AccountId);
}
updateAccounts(aId);
}
private void updateAccounts(Set<Id> accIds){
List<Account> accs = [select id, Number_Of_Opps__c, (select Id from Opportunities) from Account where Id in :accIds];
List<Account> liAccToUpdate = new list<Account>();
//Should be able to handle more than one opportunity/account in the trigger.
for(Account a : accs){
//Prevent recursive updating
if(a.Number_Of_Opps__c != a.Opportunities.size()){
a.Number_Of_Opps__c = a.Opportunities.size();
liAccToUpdate.add(a);
}
}
if(liAccToUpdate != NULL && liAccToUpdate.size() > 0){
update accs;
}
}
}
public static void opportunityCount(List<opportunity> oppList) {
set<id>accountIDs = new set<id>();
list<Account> accList = new List<Account>();
for (opportunity eachOppAccId:oppList) {
accountIDs.add(eachOppAccId.AccountId);
}
for (Account eachAccount:[select id,No_Of_Opportunities__c, (select id, AccountID from opportunities) from Account where id In:accountIDs]) {
eachAccount.No_Of_Opportunities__c=eachAccount.opportunities.size();
accList.add(eachAccount);
}
update accList;
}

How to Create a read-only group in SalesForce

I want to know how to create a read-only group + like + comment and not being able to post for members except admin and owner
* how to use triggers on the posting in this group?.
i tried but it not work:
trigger N_GroupReadOnly on FeedItem (before insert) {
ID groupId = [Select Id from CollaborationGroup where Name = 'Group_ReadOnly'].Id;
CollaborationGroup ownerId = [Select OwnerId From CollaborationGroup Where Name = 'Group_ReadOnly'];
for(FeedItem item : trigger.new){
if((item.ParentId == groupId) && (item.InsertedById != ownerId.OwnerId)){
system.debug('you can not add post in this group');
alert("you can not add post in this group");
delete item ;
return;
}
else{
insert item;
}
}
}
Thank you.
Solution:
as per this developerforce.com forum entry:
Trigger
trigger GroupReadOnly on FeedItem (before insert) {
CollaborationGroup gp = [Select OwnerId, Id From CollaborationGroup Where Name = 'Group_ReadOnly'];
List<FeedItem> feedItems = new List<FeedItem>();
for(FeedItem item : trigger.new){
if(item.ParentId == gp.Id)
{
feedItems.add(item);
}
}
if(feedItems.size() >0) GroupReadOnlyClass.FilterFeedItems(feedItems);
}
Class
public class GroupReadOnlyClass{
public static void FilterFeedItems(List<FeedItem> feedItems){
CollaborationGroup gp = [Select OwnerId, Id From CollaborationGroup Where Name = 'Group_ReadOnly'];
for(FeedItem item :feedItems){
if(item.ParentId == gp.Id)
{
if(UserInfo.getUserId()!= gp.OwnerId){
item.addError('You cannot post! Just Owner can post in this group');
}
}
}
}
}

automatically insert in a master relationship field

I have a VF page with a master relationship field, can I insert value in this field automatically
<apex:inputField value="{!a.Parent__c}"/>
Question is a bit vague but if you want to set a default value for the Parent__c you can just apply the value in your controller class.
EXAMPLE
public class VFController {
private Account controllerAccount;
//Basic Constructor
public VFController() {
controllerAccount = new Account();
controllerAccount.Parent__c = getDefaultParentAccount();
}
//Standard Controller Constructor
public VFController(ApexPages.Controller standardController) {
controllerAccount = (Account)standardController.getRecord();
controllerAccount.Parent__c = getDefaultParentAccount();
}
private Id getDefaultParentAccount() {
Id parentAccountId = null;
//Lookup default parent Account
List<Account> parentAccounts = [SELECT Id FROM Account WHERE /*Your Criteria here*/ LIMIT 1];
if (parentAccounts.isEmpty() == false) {
parentAccountId = parentAccounts[0].Id;
}
return parentAccountId;
}
}

Resources