Trailhead Superbadge Logic to create Junction Object - salesforce

I have a challenge to complete. The requirement is automate record creation of a new Case record(renamed maintenance request). When an existing maintenance request(Case record) of type Repair or Routine Maintenance is closed, create a new maintenance request(Case record) for a future routine checkup. This new maintenance request is tied to the same Vehicle and Equipment(Product2- Standard Object renamed) Records as the original closed request. There is a logic on the due date field. It should have the min date compared to all the maintenance cycle of the equipment record related to the maintenance request.
I separated the logic in a helper class for the trigger. I believe the helper class code logic for 'Equipment Maintenance Item' record creation is not the right approach but trailhead already accepted it and provided me 500 points. Please help me find the right approach for the new 'Equipment Maintenance Item' record creation logic.
(The 'Equipment Maintenance Item' object is a Junction Object)
image
2 standard objects are used:
Maintenance Request (renamed Case) and Equipment (renamed Product)
2 custom objects are used:
Vehicle and Equipment Maintenance Item
Trigger:
trigger MaintenanceRequest on Case (before update, after update) {
if(Trigger.isAfter){
MaintenanceRequestHelper.updateWorkOrders(Trigger.New);
}}
Helper Class:
public with sharing class MaintenanceRequestHelper {
public static void updateWorkOrders(List<Case> CaseList) {
integer i=0;
List<Case> NewCaseList = new List<Case>();
List<Equipment_Maintenance_Item__c> eList = [SELECT Id,Maintenance_Request__c,Equipment__c,Quantity__c
FROM Equipment_Maintenance_Item__c
WHERE Maintenance_Request__c IN: CaseList];
List<Aggregateresult> mindue = new List<Aggregateresult>([SELECT MIN(Equipment__r.Maintenance_Cycle__c)MinimumValue
FROM Equipment_Maintenance_Item__c WHERE Maintenance_Request__c IN: CaseList]);
integer k=0;
List<Equipment_Maintenance_Item__c> newEmi = new List<Equipment_Maintenance_Item__c>();
for(Case c : CaseList){
if(c.Status == 'Closed' && (c.type =='Repair' || c.type =='Routine Maintenance')){
Case c1 = new Case(
Status = 'New',
Vehicle__c = c.Vehicle__c,
Type = 'Routine Maintenance',
Subject = 'Routine Checkup',
Date_Reported__c = Date.today(),
Product__c = c.Product__c,
AccountId = c.AccountId,
ContactId = c.ContactId,
Origin = c.Origin
//Date_Due__c = Date.today()
);
NewCaseList.add(c1);
for(Equipment_Maintenance_Item__c emi : eList){
if(c.Id == emi.Maintenance_Request__c){
newEmi.add(new Equipment_Maintenance_Item__c(
Equipment__c = emi.Equipment__c,
Maintenance_Request__c = c.Id,
Quantity__c = emi.Quantity__c));
}
}
for(Aggregateresult r:mindue){
if(r.get('MinimumValue')!=NULL){
NewCaseList[i].Date_Due__c=system.today()+integer.valueof(r.get('MinimumValue'));
}
i++;
}
}
}
if(NewCaseList.size()>0){
insert NewCaseList;
}
for(Case c2: NewCaseList){
for(Equipment_Maintenance_Item__c emi2 : newEmi){
emi2.Maintenance_Request__c = c2.id;
}
}
insert newEmi;
}
}

Your Code is correct but not bulkified. It will work for a single record and it will fail in case a bunch of records closed at the same time. I have solved it by creating one field on the Case object which will store cloned Case Id i.e. the old case from which the new case has been created.
Please refer code sample below:
trigger MaintenanceRequest on Case (before update, after update) {
//ToDo: Call MaintenanceRequestHelper.updateWorkOrders
if(trigger.isAfter){
MaintenanceRequestHelper.updateWorkOrders(trigger.new);
}
}
public with sharing class MaintenanceRequestHelper {
public static void updateWorkOrders(List<Case> newList) {
// TODO: Complete the method to update workorders
List<Case> CreateMaintReqLst = new List<Case>();
Set<Id> caseIdsSet = new Set<Id>();
List<Equipment_Maintenance_Item__c> newEMI = new List<Equipment_Maintenance_Item__c>();
for (Case caseRec : newList)
{
if(caseRec.Status == 'Closed' &&
(caseRec.Type =='Repair' || caseRec.Type =='Routine Maintenance'))
{
caseIdsSet.add(caseRec.Id);
}
}
List<Case> CaseList = [SELECT Id,Type,Status,Vehicle__c,Subject,Date_Reported__c,Date_Due__c,ProductId,Product__c,
(select Id,Maintenance_Cycle__c,Equipment__c,Quantity__c FROM Equipment_Maintenance_Items__r)
FROM Case
WHERE Id in:caseIdsSet];
for(Case caseRec:CaseList){
Integer minMaintCycle = 0;
List<Equipment_Maintenance_Item__c> EqpMaintList = caseRec.Equipment_Maintenance_Items__r;
if(EqpMaintList.size()>0){
for(Equipment_Maintenance_Item__c EquipMaint:EqpMaintList){
newEMI.add(new Equipment_Maintenance_Item__c(
Equipment__c = EquipMaint.Equipment__c,
Maintenance_Request__c = caseRec.Id,
Quantity__c = EquipMaint.Quantity__c));
if(Integer.valueOf(EquipMaint.Maintenance_Cycle__c) < minMaintCycle || minMaintCycle == 0){
minMaintCycle =Integer.valueOf(EquipMaint.Maintenance_Cycle__c);
}
}
}
Case newCase = new Case();
newCase.Type = 'Routine Maintenance';
newCase.Status = 'New';
newCase.Vehicle__c = caseRec.Vehicle__c;
newCase.Subject = String.isBlank(caseRec.Subject) ? 'Routine Maintenance Request' : caseRec.Subject;
newCase.Date_Reported__c = Date.today();
newCase.Date_Due__c = Date.today().addDays(minMaintCycle);
newCase.ProductId = caseRec.ProductId;
newCase.Product__c = caseRec.Product__c;
newCase.Cloned_Closed_Case_Id__c = caseRec.Id;
CreateMaintReqLst.add(newCase);
}
if(CreateMaintReqLst.size()>0){
Database.insert(CreateMaintReqLst);
}
for(Case c2: CreateMaintReqLst){
for(Equipment_Maintenance_Item__c emi2 : newEmi){
if(c2.Cloned_Closed_Case_Id__c == emi2.Maintenance_Request__c){
emi2.Maintenance_Request__c = c2.id;
}
}
}
if(newEmi.size()>0){
Database.insert(newEmi);
}
}
}

Related

Can someone help in Bulkifying the below Apex code. The purpose here is to Remove Product Sharing When user is removed from AccountTeamMember

The purpose here is to Remove Product Sharing When user is removed from AccountTeamMember.
List<AccountTeamMember> acctmListProd = [Select id,UserId, AccountId, TeamMemberRole FROM
AccountTeamMember WHERE Id In:acctmList and
TeamMemberRole IN:Roles]
Map<Id,Id> accToUserIdList = new Map<Id,Id>();
for(AccountTeamMember At: acctmListProd)
{
accToUserIdList.put(At.AccountId, At.UserId);
}
List<Product__Share> DelProdShareRecords = new List<Product__Share>();
Set<Id> productIds = new Set<Id>();
for(Id accId: accToUserIdList.keySet())
{
List<Product__c> prodList = [Select id,Account__c from Product__c where
Account__c=accId];
for(Product__c prod: prodList)
{
productIds.add(prod.Id);
}
List<Product__Share> prodShareRecords = [Select id,ParentId,UserOrGroupId from
Product__Share where ParentId IN:productIds AND
UserOrGroupId=accToUserList.get(accId)
];
DelProdShareRecords.addAll(prodShareRecords);
}
if(!DelProdShareRecords.isEmpty())
{
Database.deleteResult[] result = Database.delete(DelProdShareRecords, false);
}
https://ideas.salesforce.com/s/idea/a0B8W00000GdgUVUAZ/allow-global-security-and-sharing-rules-settings-on-products
Product__Share isn't a thing. This code looks like it was never compiled or tested in an org.

Apex Specialist Challenge 1 too Many DML Rows

I'm Working through this challenge and appear to have run into an issue i have not found a solution to (nor does it appear that anyone has posted a question on this specifically.)
looking at the log is pretty painful as any System.debug() lines appear to be dropped due to the size of the log being >18MB but it appears there are 40000 rows to be added. Having caved and looked at some of the other code dumps out there i dont see that anyone else looks to have had this issue or made any special arrangements with batching and as far as i can see i haven't done anything drastically different to them (but the subtlety is clearly more than enough to cause an issue).
So if anyone could have a look over this and give me a hint as to where/why i have gone wrong it would be a great help. I'm not looking for a link to a working code set to copy i'm hoping for some understanding as to where my approach is incorrect/causing issues
With my trigger
trigger MaintenanceRequest on Case ( after update) {
List<Case> closedCases = new List<Case>();
for (Case c : Trigger.New){
System.debug('Type :' + c.Type );
System.debug('Equals Repair: ' +c.Type.equalsIgnoreCase('Repair'));
System.debug('Equals Routine Maintenance: ' +c.Type.equalsIgnoreCase('Routine Maintenance'));
if (
(c.Type.equalsIgnoreCase('Repair') || c.Type.equalsIgnoreCase('Routine Maintenance'))
&& c.isClosed == true )
{
closedCases.add(c);
}
}
System.debug(closedCases.size() + ' cases were closed');
if(closedCases.size() > 0){
MaintenanceRequestHelper.createScheduledMaintainance(closedCases);
}
}
and helper
public with sharing class MaintenanceRequestHelper {
public MaintenanceRequestHelper() {
}
public static Map<Id,Integer> getMainainanceCycleForCases(){
List<AggregateResult> maintainTimes = [SELECT Maintenance_Request__c, MIN(Equipment__r.Maintenance_Cycle__c)cycle
FROM Equipment_Maintenance_Item__c
GROUP BY Maintenance_Request__c
];
System.debug(maintainTimes);
Map<Id,Integer> maintinTimeMap = new Map<Id,Integer>();
for( AggregateResult e : maintainTimes ){
maintinTimeMap.put((Id)e.get('Maintenance_Request__c'), ((Double)e.get('cycle')).intValue());
}
System.debug(maintinTimeMap);
return maintinTimeMap;
}
public static Map<Id,List<Id>> getMaintainanceItems(List<Case> closedCases){
// map maintainance_Request_ID, Equipment_Maintenance_Item__c
List<Equipment_Maintenance_Item__c> equipmentMaintainanceList = [SELECT Maintenance_Request__c,
Equipment__r.Id
FROM Equipment_Maintenance_Item__c
Where Maintenance_Request__c IN :closedCases];
Map<Id,List<Id>> maintainance_equipmentMap = new Map<Id, List<Id>>();
for (Equipment_Maintenance_Item__c e:equipmentMaintainanceList){
System.debug('Putting : '+ e.Maintenance_Request__c + ' : ' + e.Equipment__r.Id);
if(maintainance_equipmentMap.containsKey(e.Maintenance_Request__c)){
maintainance_equipmentMap.get(e.Maintenance_Request__c).add(e.Equipment__r.Id);
}else{
maintainance_equipmentMap.put(e.Maintenance_Request__c,new List<id>{e.Equipment__r.Id});
}
System.debug('Map now : '+ maintainance_equipmentMap);
}
System.debug('Returning: ' +maintainance_equipmentMap);
return maintainance_equipmentMap;
}
public static void createScheduledMaintainance(List<Case> closedCaseList) {
System.debug(closedCaseList.size() + ' Cases to create');
Map<Id,Integer> maintainTimeMap = getMainainanceCycleForCases();
Map<Id,List<Id>> maintainanceItems = getMaintainanceItems(closedCaseList); // map maintainance_Request_ID, Equipment_Maintenance_Item__c
List<Case> createNewCases = new List<Case>();
for(Case c : closedCaseList){
Case newCase = new Case();
newCase.Type = 'Routine Maintenance';
newCase.Status = 'New';
newCase.Vehicle__c = c.Vehicle__c;
newCase.Subject = c.Subject;
newCase.Date_Reported__c = Date.today();
newCase.Date_Due__c = Date.today() + maintainTimeMap.get(c.Id);
newCase.ProductId = c.ProductId;
createNewCases.add(newCase);
}
System.debug(createNewCases.size() +' to insert');
list<Equipment_Maintenance_Item__c> itemsListToinsert= new list<Equipment_Maintenance_Item__c>();
if(createNewCases.size()>0){
insert createNewCases;
for(Case ca : createNewCases){
for(Id key: maintainanceItems.keySet()){
List<Id> equipment = maintainanceItems.get(key);
for (Id e : equipment){
Equipment_Maintenance_Item__c newitem = new Equipment_Maintenance_Item__c();
newitem.Equipment__c=e;
newitem.Maintenance_Request__c= ca.Id;
itemsListToinsert.add(newitem);
}
}
}
}
System.debug('itemsListToinsert Size: ' +itemsListToinsert.size());
if (itemsListToinsert.size() >0){
insert itemsListToinsert; //<<<< ERROR TRIGGERS HERE and has ~40000 rows <<<<<
}
}
}
So, It seems i was over complicating the issue to an extent.
Having taken a break and walking through the problem again something just didnt sit right with this block of code.
if(createNewCases.size()>0){
insert createNewCases;
for(Case ca : createNewCases){
for(Id key: maintainanceItems.keySet()){
List<Id> equipment = maintainanceItems.get(key);
for (Id e : equipment){
Equipment_Maintenance_Item__c newitem = new Equipment_Maintenance_Item__c();
newitem.Equipment__c=e;
newitem.Maintenance_Request__c= ca.Id;
itemsListToinsert.add(newitem);
}
}
}
}
For -> For -> For -> create new Equipment_Maintenance_Item__c
With 0 checks to see if this new Equipment_Maintenance_Item__c was needed.
I ended up partly re-writing the class but the main change was re-working the section of code above to
List<Equipment_Maintenance_Item__c> equipmentMaintainanceList = [SELECT Maintenance_Request__c,
Equipment__r.Id
FROM Equipment_Maintenance_Item__c
Where Maintenance_Request__c IN :closedCaseList];
List<Equipment_Maintenance_Item__c> updatedEquipment = new List<Equipment_Maintenance_Item__c>();
for(Case closed : closedCaseList){
for(Equipment_Maintenance_Item__c eqip : equipmentMaintainanceList){
if (eqip.Maintenance_Request__c == closed.Id){
Equipment_Maintenance_Item__c e = eqip.clone(false, false, false, false);
e.Maintenance_Request__c = oldToNewMap.get(closed.Id).Id;
updatedEquipment.add(e);
}
}
}
if (updatedEquipment.size() > 0 ){
insert updatedEquipment;
}
For me the lesson is be very mindful of nested for loops when the outcome is going to come up against some hard and fast governor limits.

Salesforce- retrieve Contact.opportunites

Here is snippet I am using in my anonymous window . Purpose is to retrieve Opportunities of a contact. Even after adding opportunity contact role , contact.Opportunities.size is resulting in zero (last debug line). Am I missing something ? you may use the below code directly.
Update: able to get size now but same logic doesn't work for code coverage in test class . details listed below:
Only 'if' part of controller is covered and 'else' part is never covered even though size of contact.opportunities is more than 0.
Controller method :
public PageReference sendingEmail() {
//contact1 has query records
sizeVar = contact1.Opportunities.size();
if(contact1.npo02__OppAmountLastYear__c>0 ) {
if(sizeVar==0) {
// if size is 0 then navigate to a particular vf page
PageReference pr = Page.NoDonationOrNoEmail;
pr.getParameters().put('id',(String)contact1.id);
pr.setRedirect(true);
return pr;
}
else
{ //when contact.opportunities size is more than 0 then navigate to
other vf page.
PageReference pr1 = Page.NoPrint;
pr1.getParameters().put('id',(String)contact1.id);
pr1.setRedirect(true);
return pr1;
}
} return null;
}
Test Class:
//creating account
Account a = new Account();
a.Name = 'Test Co.';
a.BillingStreet = '298 S. Ringo Street';
a.BillingCity = 'Little Rock';
insert a;
//Creating contact
Contact contact1 = new Contact();
contact1.FirstName = 'Paul';
contact1.LastName = 'Test';
contact1.AccountId = a.id;
contact1.npo02__OppAmountLastYear__c=100;
insert contact1;
//creating opportunity
Opportunity o = new Opportunity();
o.RecordType = [SELECT Id, Name, DeveloperName FROM RecordType
WHERE Name = 'Membership' LIMIT 1];
o.Name = 'New Record';
o.StageName = 'Posted';
o.AccountId = contact1.AccountId;
o.CloseDate = Date.today();
o.Description = 'Test Record';
insert o;
//creating opportunity contact role
OpportunityContactRole ocr = new OpportunityContactRole();
ocr.ContactId = contact1.Id;
ocr.OpportunityId = o.Id;
ocr.IsPrimary = TRUE;
ocr.Role = 'Decision Maker';
insert ocr;
System.debug('created opportunity contact role for primary');
Update o;
contact1 = [SELECT Id, Name,(SELECT id FROM opportunities) FROM
Contact WHERE Id=:contact1.Id];
PageReference pr = Page.NoPrint;
pr.getParameters().put('id', String.valueOf(contact1.id));
Test.setCurrentPage(pr);
ApexPages.StandardController cont5 = new
ApexPages.StandardController(contact1);
BulkEmailController testAccPlan = new
BulkEmailController(cont5);
testAccPlan.sendingEmail();
When you create and insert a record, you don't get formula fields nor you can navigate lookup. The same goes with child relationship.
You have to query the fields you need.
Change Update contact1; to contact1 = [SELECT Id, (SELECT Id FROM Opportunities) FROM Contact WHERE Id = :contact1.Id]; and the last debug line will print 1.

How to test contentdocumentlink trigger for Salesforce Prod deployment

I am trying to deploy a trigger to prod on salesforce. I was hoping someone could help me with an example of tests for this trigger.
Here is my trigger. It does its purpose, which is to update a bool field when a new contentNote (or anything of content type) that then has collateral effects through process builder.
trigger NewNote on ContentDocumentLink (before insert) {
Set<Id> setParentId = new Set<Id>();
List<Client_Relationships__c> crlst = new List<Client_Relationships__c>();
for (ContentDocumentLink cdl : trigger.new ) {
setParentId.add(cdl.LinkedEntityId);
}
crlst = [select Id , newNote__c from Client_Relationships__c where Id IN :setParentId];
For(Client_Relationships__c e : crlst)
{
e.newNote__c = True;
}
update crlst;
}
The trigger you wrote can be more efficient by omitting the SOQL query as seen below:
trigger NewNote on ContentDocumentLink (before insert) {
List<Client_Relationships__c> crlst = new List<Client_Relationships__c>();
for (ContentDocumentLink cdl : trigger.new ) {
if(cdl.LinkedEntityId.getSObjectType().getDescribe().getName() == 'Client_Relationships__c'){
crlst.add(
new Client_Relationships__c(
Id = cdl.LinkedEntityId,
newNote__c = true
)
);
}
}
update crlst;
}
The best practice would be to add your code to a handler or utility class and to only have one trigger per object. The name of this trigger could be changed to "ContentDocumentLinkTrigger" if you adopt that practice.
The test class for that trigger is below. I could not test the compilation because I don't have the same custom object.
#IsTest
private class ContentDocumentLinkTriggerTest {
#TestSetup
static void setupTest() {
insert new ContentVersion(
Title = 'Test_Document.txt',
VersionData = Blob.valueOf('This is my file body.'),
SharingPrivacy = 'N',
SharingOption = 'A',
Origin = 'H',
PathOnClient = '/Test_Document.txt'
);
List<Client_Relationships__c> relationships = new List<Client_Relationships__c>();
for(Integer i = 0; i < 300; i++){
relationships.add(
new Client_Relationships__c(
//add required field names and values
)
);
}
insert relationships;
}
static testMethod void testInsertTrigger() {
//prepare data
List<ContentVersion> contentVersions = new List<ContentVersion>([
SELECT Id, ContentDocumentId FROM ContentVersion
]);
System.assertNotEquals(0, contentVersions.size(), 'ContentVersion records should have been retrieved');
List<Client_Relationships__c> relationships = getAllClientRelationships();
System.assertNotEquals(0, relationships.size(), 'Client Relationship records should have been retrieved.');
List<ContentDocumentLink> documentLinks = new List<ContentDocumentLink>();
for(Integer i = 0; i < 252; i++){
documentLinks.add(
new ContentDocumentLink(
ContentDocumentId = contentVersions[0].ContentDocumentId,
LinkedEntityId = relationships[i].Id,
ShareType = 'I'
)
);
}
//test functionality
Test.startTest();
insert documentLinks;
Test.stopTest();
//assert expected results
List<Client_Relationships__c> relationshipsAfterProcessing = getAllClientRelationships();
for(Client_Relationships__c relationship : relationshipsAfterProcessing){
System.assert(relationship.newNote__c, 'The newNote__c field value should be true.');
}
}
private static List<Client_Relationships__c> getAllClientRelationships(){
return new List<Client_Relationships__c>([
SELECT Id, newNote__c FROM Client_Relationship__c
]);
}
}
For setting up test data, it is helpful to have a utility class that centralizes the creation of well-formed records. This is extremely useful when your code base gets large and a validation rule affects the insertion of new data in many test classes. With a centralized method, the inserted data only needs to be altered once.

Share primary Contacts along with the Opportunity using Sales Force to Sales Force Connector

Lead - gets converted to an Account , Contact and an Opportunity
I developed a trigger which shares an Opportunity and related Account with another Org of ours, and the piece i am missing is sharing the Contact along with this . Need some help for sharing the contact also.
Trigger autoforwardOpportunity on Opportunity(after insert) {
String UserName = UserInfo.getName();
String orgName = UserInfo.getOrganizationName();
List<PartnerNetworkConnection> connMap = new List<PartnerNetworkConnection>(
[select Id, ConnectionStatus, ConnectionName from PartnerNetworkConnection where ConnectionStatus = 'Accepted']
);
System.debug('Size of connection map: '+connMap.size());
List<PartnerNetworkRecordConnection> prncList = new List<PartnerNetworkRecordConnection>();
for(Integer i =0; i< Trigger.size; i++) {
Opportunity Opp = Trigger.new[i];
String acId = Opp.Id;
System.debug('Value of OpportunityId: '+acId);
for(PartnerNetworkConnection network : connMap) {
String cid = network.Id;
String status = network.ConnectionStatus;
String connName = network.ConnectionName;
String AssignedBusinessUnit = Opp.Assigned_Business_Unit__c;
System.debug('Connectin Details.......Cid:::'+cid+'Status:::'+Status+'ConnName:::'+connName+','+AssignedBusinessUnit);
if(AssignedBusinessUnit!=Null && (AssignedBusinessUnit.equalsIgnoreCase('IT') || AssignedBusinessUnit.equalsIgnoreCase('Proservia'))) {
// Send account to IT instance
PartnerNetworkRecordConnection newAccount = new PartnerNetworkRecordConnection();
newAccount.ConnectionId = cid;
newAccount.LocalRecordId = Opp.AccountId;
newAccount.SendClosedTasks = true;
newAccount.SendOpenTasks = true;
newAccount.SendEmails = true;
newAccount.RelatedRecords = 'Contact';
System.debug('Inserting New Record'+newAccount);
insert newAccount;
// Send opportunity to IT instance
PartnerNetworkRecordConnection newrecord = new PartnerNetworkRecordConnection();
newrecord.ConnectionId = cid;
newrecord.LocalRecordId = acId;
newrecord.SendClosedTasks = true;
newrecord.SendOpenTasks = true;
newrecord.SendEmails = true;
//newrecord.ParentRecordId = Opp.AccountId;
System.debug('Inserting New Record'+newrecord);
insert newrecord;
}
}
}
}
newrecord.RelatedRecords = 'Contact,Opportunity'; //etc

Resources