4

I am trying to utilize DLRS with objects that are used in the Field Service Lightning managed package. More specifically, I am trying to total the ActualDuration field from the ServiceAppointment on the parent WorkOrder in a custom field called TotalDuration__c. This will be used for reporting, where if there is more than one ServiceAppointment associated to the WorkOrder (ex: return trip needed, multiple service resources), we can see the total actual duration from just the WorkOrder.

The issue I am experiencing is that the default child trigger and test class that the solution attempts to generate and deploy is giving me an error:

dlrs_ServiceAppointmentTest.testTrigger System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, FSL.TR001_Service_BeforeInsert: execution of BeforeInsert caused by: System.NullPointerException: Attempt to de-reference a null object Class.FSL.TR001_ServiceUtils.assignDurationAndDDOffsetByWorkType: line 1047, column 1 Trigger.FSL.TR001_Service_BeforeInsert: line 13, column 1: [] Class.dlrs.RollupService.testHandler: line 282, column 1 Class.dlrs_ServiceAppointmentTest.testTrigger: line 11, column 1

And then the subsequent warning:

dlrs_ServiceAppointmentTrigger:Test coverage of selected Apex Trigger is 0%, at least 75% test coverage is required

In Field Service Lightning (FSL), the ServiceAppointment is auto-generated when a WorkOrder is created, and at this time, the WorkType is passed from the WorkOrder to the ServiceAppointment.

From what I can tell, there is a validation rule on the ServiceAppointment that requires the WorkType be populated, which in turn populates the Duration and DueDateOffSet on the ServiceAppointment record.

In my attempt to work through this myself, I have tried writing my own class and test class. I am beginning to learn Apex, so bear with me, I know this is verbose:

public class CreateServiceAppointment{
public ServiceAppointment createServiceAppointment(){

    Account acc = new Account();
    acc.name = 'New Account Name' + datetime.now();
    acc.description = 'New Account Description' + datetime.now();
    insert acc;

    Contact con = new Contact();
    con.firstName = 'New Contact First';
    con.lastName = 'New Contact Last' + datetime.now();
    con.email = 'hello@example.com';
    con.phone = '4805551212';
    con.title = 'New Contact Title' + datetime.now();
    con.accountId = acc.id;
    insert con;

    Case ca = new Case();
    ca.accountId = acc.id;
    ca.contactId = con.id;
    ca.status = 'New';
    ca.origin = 'Email';
    ca.subject = 'New Case Subject' + datetime.now();
    ca.description = 'New Case Description' + datetime.now();
    insert ca;

    WorkOrder wo = new WorkOrder();
    wo.subject ='New Work Order Subject' + datetime.now();
    wo.description = 'New Work Order Description' + datetime.now();
    wo.caseId = ca.id;
    wo.accountId = acc.id;
    wo.contactId = con.id;
    wo.workTypeId = '08qf4000000QMi0AAG';
    insert wo;

    ServiceAppointment sa = new ServiceAppointment();
    sa.parentRecordId = wo.id;
    sa.earliestStartTime = datetime.now();
    sa.dueDate = datetime.now() + 30;
    return sa;
    }
}

And then my test class:

@isTest
public class TestServiceAppointment {
    static testMethod void CreateServiceAppointment()
    {
        CreateServiceAppointment cSA = new CreateServiceAppointment();
        cSA.createServiceAppointment();
    }
}

I have two issues at hand.

  1. First issue is when I am working on the class in Dev Console, it says there is 1 Problem, and that the problem is "duplicate value found: duplicates value on record with id: ". From what I can tell, this could be anything. Is there anything glaringly inaccurate with my code that would be causing this?

  2. Second issue, am I going about this the right way to solve for my original error and warning?

I appreciate any help offered.

2

For the dev console error, it might have to do with the naming of your Apex class. Is there already a class with that name? Does the class name mismatch the filename? And maybe don't give the method the same name as the class.


If you look at the DLRS code you can maybe get a hint of what is going on:

https://github.com/afawcett/declarative-lookup-rollup-summaries/blob/master/force-app/main/classes/RollupService.cls

The auto-generated test code includes: dlrs.RollupService.testHandler(new ServiceAppointment()); or whatever is the name of the object you rolled up.

See line 279 global static void testHandler(SObject dummyChildRecord)

It has NO possible way to guarantee it can insert/update a valid record, so what it does is passes in a totally blank SObject. Attempting to insert it will ALWAYS throw an exception, but typically before that happens, the before insert triggers get a chance to run. So it does a try/catch and if it determines the DLRS trigger ran, it's happy and has coverage.

However, pre-existing managed package triggers (like FSL) typically run first. The FSL trigger is designed to work only with valid objects and so it bombs out on a null exception when it tries to work with a required field and finds it blank. My best guess is, FSL's trigger tries to use an Apex method on a non-nillable field, e.g. Id.getSObjectType() on OwnerId, or a DateTime related method on DueDate.

So yeah this is a reasonable approach. In your sandbox, prepare the trigger with a test class that actually does work. You need to insert something valid enough to complete the DML transaction. Then you can deploy together to production.

Alternately your test class can take the same form as the test code, except you'd fill in enough required fields of the dummy ServiceAppointment that it doesn't bomb out on FSL's before triggers. The insert doesn't have to succeed, just has to make it far enough to execute the trigger.

0
0

@Charles T

Thank you so much! The error was the naming. I had changed it part way through writing it, so the actual class name of the file in the dev console vs. the class name in the code was different.

Everything appears to be working correctly with the DLRS package now. In case anyone else stumbles onto this, and is trying to use this package with Service Appointments, my solution was to create my own class and my own test class, but utilize the OOTB DLRS trigger.

Basically Create Account > Contact > Case > WorkOrder (w/WorkType) > ServiceAppointment

Here's my class:

@isTest
public class CreateServiceAppointment {
public ServiceAppointment createServiceAppointment(){

    Account acc = new Account();
    acc.name = 'New Account Name' + datetime.now();
    acc.description = 'New Account Description' + datetime.now();
    insert acc;

    Contact con = new Contact();
    con.firstName = 'New Contact First';
    con.lastName = 'New Contact Last' + datetime.now();
    con.email = 'hello@example.com';
    con.phone = '4805551212';
    con.title = 'New Contact Title' + datetime.now();
    con.accountId = acc.id;
    insert con;

    Case ca = new Case();
    ca.accountId = acc.id;
    ca.ContactId = con.id;
    ca.status = 'New';
    ca.origin = 'Email';
    ca.subject = 'New Case Subject' + datetime.now();
    ca.description = 'New Case Description' + datetime.now();
    insert ca;

    WorkOrder wo = new WorkOrder();
    wo.subject ='New Work Order Subject' + datetime.now();
    wo.description = 'New Work Order Description' + datetime.now();
    wo.caseId = ca.id;
    wo.accountId = acc.id;
    wo.contactId = con.id;
    wo.workTypeId = '08qf4000000QMi0AAG';
    insert wo;

    ServiceAppointment sa = new ServiceAppointment();
    sa.parentRecordId = wo.id;
    sa.earliestStartTime = datetime.now();
    sa.dueDate = datetime.now() + 30;

    return sa;
    }
}

Here is my test class:

@isTest
public class TestServiceAppointment {
    static testMethod void CreateServiceAppointment()
    {
        CreateServiceAppointment cSA = new CreateServiceAppointment();
        cSA.createServiceAppointment();
    }
}

Here is the auto-generated DLRS trigger:

/**
 * Auto Generated and Deployed by the Declarative Lookup Rollup Summaries Tool package (dlrs)
 **/

trigger dlrs_ServiceAppointmentTrigger on ServiceAppointment
    (before delete, before insert, before update, after delete, after 
insert, after undelete, after update)
{
    dlrs.RollupService.triggerHandler(ServiceAppointment.SObjectType);
}

I highly doubt this is the cleanest or most efficient way of doing this, but it worked for me. I'm not trying to do any complex rollup calculations, so I can't say if it will work for everyone. YMMV.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.