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
bdardinbdardin 

Modifying Managed Package Code

We've recently installed Timba Surveys. We want to send out a survey to the customer every time a work order, a custom object, is completed. More importantly, we want to relate their response back to the work order it came from. I already contacted Timba Surveys support and they said they don't support it so I was hoping to find a workaround.

 

The idea I had was pretty simple - pass the work order ID into the URL of the survey, then grab that ID and insert it into a lookup field on the survey response object. I was hoping I would be able to copy the visualforce page code and just add a new extension that does this, since I don't want to touch anything else. But when I try to copy the code and save it, I get this error: "Error: Constructor is not visible: [TIMBASURVEYS.SurveyController]<init>(ApexPages.StandardController)". I understand that it's not visible because it's managed, but it was the only idea I had. Is there any way around this or any other way of making this work? It's critically important for us to be able to relate it back to this custom object.

Jeff MayJeff May

You can't change Managed code (and actually can't even see most of it).  If Timba doesn't support it, your only hope is to figure out a way to trick the Timba code into doing what you need by setting fields and flags on your records.

bdardinbdardin

I figured as much but was just hoping for ideas. I actually did have another idea, based on the assumption that when the customer fills out the survey, they will be thinking of the most recently completed work order, since I can't otherwise get the ID. So I wrote a trigger that queries the work orders related to the contact and then gets the most recent work order ID and updates the lookup field with it. The trigger worked fine the first time the contact filled out the survey, but it didn't work when I created another work order with the same contact and filled it out again, which we expect to happen. I'm trying to figure out what I did wrong here, hopefully you can help. I passed trigger.new into this class btw:

 

public class surveySummaryTriggerClass {

    public static void beforeTrigger (List<TIMBASURVEYS__Survey_Summary__c> sums) {
        Set<ID> ids = new Set<ID>();
        for(TIMBASURVEYS__Survey_Summary__c sum : sums) {
            ids.add(sum.TIMBASURVEYS__Contact__c);
        }
    
        if(!ids.isEmpty()) {
            List<SVMXC__Service_Order__c> woList = [SELECT Id, SVMXC__Contact__c, Technician_completed_WO_Date_Time__c
                                                    FROM SVMXC__Service_Order__c
                                                    WHERE SVMXC__Contact__c IN :ids AND Technician_completed_WO_Date_Time__c != null
                                                    ORDER BY Technician_completed_WO_Date_Time__c DESC];
        
            Map<ID, List<SVMXC__Service_Order__c>> woMap = new Map<ID, List<SVMXC__Service_Order__c>>();
        
            for(SVMXC__Service_Order__c wo : woList) {
                woMap.put(wo.SVMXC__Contact__c, new List<SVMXC__Service_Order__c>{wo});
            }
        
            for(TIMBASURVEYS__Survey_Summary__c sum : sums) {
                List<SVMXC__Service_Order__c> contactWOList = woMap.get(sum.TIMBASURVEYS__Contact__c);
                SVMXC__Service_Order__c wo = contactWOList[0];
            
                if(wo != null) {
                    sum.Work_Order__c = wo.Id;
                }    
            }
        }
    }
}

 Any advice would be appreciated. Thanks!

Jeff MayJeff May

Is the wo.SVMXC__Contact__c a unique and distinct value?  If not, since you are using that as the key in your map, each time a new one is found, it will overwrite the map entry.

 

You can protect against that (since you are ordering by DESC) by checking to see if the entry exists before adding it:

 

 if (!woMap.containsKey(wo.SVMXC__Contact__c) {

    // add it

}

bdardinbdardin

Before you replied, I was about to write that the weirdest thing kept happening - I was wrong, it was kinda working, but it keeps grabbing work orders that don't even have that field specified with the completed date over a year ago and associating the summary with that instead. Then I saw this so I added it to the code, but I just tested it and it STILL keeps grabbing random contact-less work orders?! In fact, it keeps pulling the same random work order for each contact I test it with, even though the contact is definitely not associated with that particular work order. It's really bizarre. :(

Jeff MayJeff May

Maybe add a "!= null" check in your top for loop when you are building the Ids.  Just in case there is a Sum record (or two) that has a null value in the field.  This way your Id set for the where clause won't have nulls for sure.

bdardinbdardin

I tried that - as well as adding similar checks - but now it's not updating the field at all. :( Here's the current version of the code:

 

public class surveySummaryTriggerClass {

    public static void beforeTrigger (List<TIMBASURVEYS__Survey_Summary__c> sums) {
        Set<ID> ids = new Set<ID>();
        for(TIMBASURVEYS__Survey_Summary__c sum : sums) {
            if(sum.TIMBASURVEYS__Contact__c != null) {
                ids.add(sum.TIMBASURVEYS__Contact__c);
            }
        }
    
        if(!ids.isEmpty()) {
            List<SVMXC__Service_Order__c> woList = [SELECT Id, SVMXC__Contact__c, Technician_completed_WO_Date_Time__c
                                                    FROM SVMXC__Service_Order__c
                                                    WHERE SVMXC__Contact__c IN :ids AND Technician_completed_WO_Date_Time__c != null
                                                    ORDER BY Technician_completed_WO_Date_Time__c DESC];
        
            Map<ID, List<SVMXC__Service_Order__c>> woMap = new Map<ID, List<SVMXC__Service_Order__c>>();
        
            if(!woList.isEmpty()) {
                for(SVMXC__Service_Order__c wo : woList) {
                    if(!woMap.containsKey(wo.SVMXC__Contact__c)) {
                        woMap.put(wo.SVMXC__Contact__c, new List<SVMXC__Service_Order__c>{wo});
                    }
                }
        
                for(TIMBASURVEYS__Survey_Summary__c sum : sums) {
                    if(sum.TIMBASURVEYS__Contact__c != null) {
                        List<SVMXC__Service_Order__c> contactWOList = woMap.get(sum.TIMBASURVEYS__Contact__c);
                        SVMXC__Service_Order__c wo = contactWOList[0];
            
                        if(wo != null && wo.SVMXC__Contact__c == sum.TIMBASURVEYS__Contact__c) {
                            sum.Work_Order__c = wo.Id;
                        }    
                    }
                }
            }
        }
    }
}

 

bdardinbdardin

Now here's something really strange. The survey link is sent out via workflow. A task is also created on the work order at the time, just for confirmation that the email was sent (I realize that's not actually accurate but it's the closest thing that workflows offer). I had the thought of trying to use these tasks that are created as a way to update the summaries and so far it actually seems to be working. I also added an after trigger method to make sure that once it is updated, that task can't be used again. Here's the code if you're curious:

 

public class surveySummaryTriggerClass {

    public static void beforeTrigger (List<TIMBASURVEYS__Survey_Summary__c> sums) {
        Set<ID> ids = new Set<ID>();
        for(TIMBASURVEYS__Survey_Summary__c sum : sums) {
            ids.add(sum.TIMBASURVEYS__Contact__c);
        }
    
        if(!ids.isEmpty()) {
            List<Task> taskList = [SELECT Id, WhatId, WhoId, What_SObject_Type__c, Subject, CreatedDate, Status
                                   FROM Task
                                   WHERE WhoId IN :ids AND What_SObject_Type__c = 'SVMXC__Service_Order__c' AND Subject = 'Survey Sent to Customer' AND Status = 'Waiting on someone else'
                                   ORDER BY CreatedDate DESC];
        
            Map<ID, List<Task>> taskMap = new Map<ID, List<Task>>();
        
            for(Task t : taskList) {
                if(!taskMap.containsKey(t.WhoId)) {
                    taskMap.put(t.WhoId, new List<Task>{t});
                }
            }
        
            for(TIMBASURVEYS__Survey_Summary__c sum : sums) {
                List<Task> contactTaskList = taskMap.get(sum.TIMBASURVEYS__Contact__c);
                Task t = contactTaskList[0];
            
                if(t != null) {
                    sum.Work_Order__c = t.WhatId;
                }    
            }
        }
    }
    
    public static void afterTrigger (List<TIMBASURVEYS__Survey_Summary__c> sums) {
        Set<ID> ids = new Set<ID>();
        for(TIMBASURVEYS__Survey_Summary__c sum : sums) {
            if(sum.Work_Order__c != null) {
                ids.add(sum.Work_Order__c);
            }
        }
        
        if(!ids.isEmpty()) {
            List<Task> taskList = [SELECT Id, WhatId, WhoId, What_SObject_Type__c, Subject, CreatedDate, Status
                                   FROM Task
                                   WHERE WhatId IN :ids AND Subject = 'Survey Sent to Customer' AND Status = 'Waiting on someone else'];
        
            if(!taskList.isEmpty()) {
                for(Task t : taskList) {
                    t.Status = 'Completed';
                }
            
                try {
                    update taskList;
                } catch (Exception e) {
                    System.debug(e.getMessage());
                }
            }
        }
    }
}

 I'm not marking this as a solution as I never got it to work the original way, and I'd certainly like to know why the original way wasn't working - so if you do know, please post so that everyone can see it, because I know eventually I won't be the only one having a problem like this!

 

JeffM - thank you SO much for taking the time to respond! :)

bdardinbdardin

I forgot to mention that I added other code to our task trigger to update the WhoId on the Task to the contact on the work order. That trigger is also what populates the What_SObject_Type__c field here (I wish Salesforce had a simpler way of checking the type of object than a trigger like this, but it certainly works). Just clarifying in case anyone gets confused. :)

sfdcfoxsfdcfox
What.Type tells you the type of object (and can be used as a filter in queries). Also, WhatId.getSObjectType() returns the concrete SObjectType of the ID held in the field in Apex Code (just make sure it's not null).