When creating a project from a template, you can copy your own custom objects. You can copy over any arrangement of custom objects related to a template using the ‘SObjectCloneMapper’ and the ‘Mappers’ property from the Create Project from Template Service.

The SObjectCloneMapper defines the structure of SObjects that you want to clone. The Create Project from  Template Service automatically uses the SObjectCloneMapper to copy over project template data in the following objects by default:

  • - Resource Request
  • - Milestone
  • - Assignment
  • - Assignment Project Phase
  • - Assignment Milestone/li>
  • - Assignment Project Methodology
  • - Project Methodology
  • - Project Task Assignment
  • - Project Phase
  • - Project Task
  • - Project Task Dependency
  • - Project Location
  • - Schedule
  • - Schedule Exception

Sample Scenario: Copying corresponding Checklist Items for Project Phases

Consider that your company has a custom object named ‘Checklist Items’ which has a lookup to the Project Phase object. Checklist Items have a checkbox field named ‘Done’. A phase is designated as finished when all Checklist Items are marked as done. You’ve added specific Phases and Checklist Items to a template and you want to include the Checklist Items when you call the Create Project from Template Service to create your new Project.

The following screenshot shows some of an example template and its Project Phases.

passing-custom-objects-1

Clicking one of the Project Phases takes you to its detail page and here are its Checklist Items in the related tab.

passing-custom-objects-2

Implementation

The following code calls the Create Project from Template Service which copies over the fields and data from the above template along with the Checklist Items into a new Project. This code can be built into a trigger, an invokable method, or any of the other customizations available on the Salesforce platform, allowing you to create a Project complete with Checklist Items without changing any of your company’s business processes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public with sharing class CPFTIncludingCheckListItems {
    public static void callCPFTIncludingCheckListItems(Id templateProjectId, Date startDate, String projectName) {
        
        //Clone mapper fields for checklist item
        //Lookup field required in order for related objects to be copied over
        pse.SObjectCloneMapper.Field itemMapperField1 = new 
            pse.SObjectCloneMapper.Field(Schema.Checklist_Item__c.Project_Phase__c);
        pse.SObjectCloneMapper.Field itemMapperField2 = new 
            pse.SObjectCloneMapper.Field(Schema.Checklist_Item__c.Name);
        pse.SObjectCloneMapper.Field itemMapperField3 = new 
            pse.SObjectCloneMapper.Field(Schema.Checklist_Item__c.Done__c);
        //Checklist items are set to unchecked as default
        itemMapperField3.DefaultValue = false;

        //Group together the pse.SObjectCloneMapper.Field instantiated above for each object and prepare a set.
        Set<pse.SObjectCloneMapper.Field> setItemMapperFields =
            new Set<pse.SObjectCloneMapper.Field>{itemMapperField1, itemMapperField2, itemMapperField3};
 
        //Instantiate the SObjectCloneMapper using the prepared set of SObjectCloneMapper.Field for each object.
        pse.SObjectCloneMapper itemCloneMap = new 
            pse.SObjectCloneMapper(Checklist_Item__c.SObjectType, setItemMapperFields);

        //Instantiate the request
        pse.CreateProjectFromTemplateService.CreateProjectFromTemplateRequest projRequest = 
            new pse.CreateProjectFromTemplateService.CreateProjectFromTemplateRequest(templateProjectId, 
            startDate);

        projRequest.ProjectName = projectName;
        projRequest.Mappers = new List<pse.SObjectCloneMapper> { itemCloneMap };
        
        //Call the service to create the projects using the request list and store a list of responses.
        List<pse.CreateProjectFromTemplateService.CreateProjectResponse> projResponses = 
            pse.CreateProjectFromTemplateService.createProjectsFromTemplates( new 
            List<pse.CreateProjectFromTemplateService.CreateProjectFromTemplateRequest>{projRequest});
    }
}

And of course, all Apex code deserve some unit tests:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@isTest
private class TestCPFTIncludingCheckListItems {
	
    @isTest static void testChecklistItems() {
        pse__Proj__c p = new pse__Proj__c();
        p.pse__Start_Date__c = Date.Today();
        p.Name = 'Test';
        p.pse__Is_Template__c = true;
        insert p;
		
        pse__Project_Phase__c phase = new pse__Project_Phase__c();
        phase.Name = 'Phase Test';
        phase.pse__Project__c = p.Id;
        insert phase;

        Checklist_Item__c ci = new Checklist_Item__c();
        ci.Name = 'Checklist Item Test';
        ci.Done__c = true;
        ci.Project_Phase__c = phase.Id;
        insert ci;

        Date d = Date.Today();
        String s = 'Test Case Project';

        Test.StartTest();
        CPFTIncludingCheckListItems.callCPFTIncludingCheckListItems(p.Id, d, s);
        Test.StopTest();
		
        Checklist_Item__c checkItem = [SELECT Name, Done__c, Project_Phase__r.Name, 
            Project_Phase__r.pse__Project__r.Name, 
            Project_phase__r.pse__Project__r.pse__Start_Date__c FROM Checklist_Item__c 
            WHERE Project_Phase__r.pse__Project__r.Name ='Test Case Project' LIMIT 1];
        
        //Check Checklist Item fields and successfully created Project and related fields
        System.assertEquals(false, checkItem.Done__c);
        System.assertEquals(Checklist Item Test, checkItem.Name);
        System.assertEquals('Phase Test',checkItem.Project_Phase__r.Name);
        System.assertEquals(s, checkItem.Project_Phase__r.pse__Project__r.Name);
        System.assertEquals(d, checkItem.Project_phase__r.pse__Project__r.pse__Start_Date__c);
    }
}

Other Possibilities

We only showed a simple example of what’s possible with the SObject Cloner. As you know, you can copy over any arrangement of custom objects and related objects from a template using the 'SObjectCloneMapper' and the 'Mappers' property of the Create Project from Template Service. To show more of what this API is capable of doing, let’s add on to our previous example with a new custom object, ‘Materials’. The Materials object refers to any materials you may need for your project. Materials also contains lookup fields to another custom object ‘Material Group’ which groups materials together. Material also has a look up to ‘Company’ and detail fields ‘Quantity’ and ‘Unit of Measurement’.

You want to copy over Materials and its fields along with the Checklist Items you’re already copying over. Fortunately, the SObjectCloneMapper allows you to do just that. Although Material Group itself doesn’t look up to project, Material does, and the API can work out which Material Groups to copy for you. On the other hand, Material also looks up to company, and we don’t want to copy companies every time we copy a project. The API can do that too, it copies the material and sets the Company lookup to reference the same Company as the template material. All this is set up in the request you make to the API.

The following screenshot shows a template and its Materials

passing-custom-objects-3

The Material Page is shown below

passing-custom-objects-4

Implementation

The following code copies the Material object along with its fields and look up to the Material Group and Company objects. The Material Group is copied over as well along with Checklist Item from the template into your new project

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public with sharing class CPFTIncludingChecklistItemsAndMaterials {
    public static void callCPFTIncludingChecklistItemsAndMaterials(Id templateProjectId, Date startDate, String projectName) {
        
        //Clone mapper fields for checklist item
        //Lookup field required in order for related objects to be copied over
        pse.SObjectCloneMapper.Field itemMapperField1 = new 
            pse.SObjectCloneMapper.Field(Schema.Checklist_Item__c.Project_Phase__c);
        pse.SObjectCloneMapper.Field itemMapperField2 = new 
            pse.SObjectCloneMapper.Field(Schema.Checklist_Item__c.Name);
        pse.SObjectCloneMapper.Field itemMapperField3 = new 
            pse.SObjectCloneMapper.Field(Schema.Checklist_Item__c.Done__c);
        //Checklist items are set to unchecked as default
        itemMapperField3.DefaultValue = false;

        //Clone mapper fields for Material
        //Lookup field required in order for related objects to be copied over
        pse.SObjectCloneMapper.Field materialMapperField1 = new 
            pse.SObjectCloneMapper.Field(Schema.Material__c.Name);
        pse.SObjectCloneMapper.Field materialMapperField2 = new 
            pse.SObjectCloneMapper.Field(Schema.Material__c.Project__c);
        pse.SObjectCloneMapper.Field materialMapperField3 = new 
            pse.SObjectCloneMapper.Field(Schema.Material__c.Quantity__c);
        pse.SObjectCloneMapper.Field materialMapperField4 = new 
            pse.SObjectCloneMapper.Field(Schema.Material__c.Unit_of_Measurement__c);
        pse.SObjectCloneMapper.Field materialMapperField5 = new 
            pse.SObjectCloneMapper.Field(Schema.Material__c.Company__c);
        pse.SObjectCloneMapper.Field materialMapperField6 = new 
            pse.SObjectCloneMapper.Field(Schema.Material__c.Material_Group__c);

        //Clone mapper fields for Material Group
        pse.SObjectCloneMapper.Field groupMapperfield1 = new
            pse.SObjectCloneMapper.Field(Schema.Material_Group__c.Name);
        
        //Group together the pse.SObjectCloneMapper.Field instantiated above for each object and prepare a set.
        Set<pse.SObjectCloneMapper.Field> setItemMapperFields =
            new Set<pse.SObjectCloneMapper.Field>{itemMapperField1, itemMapperField2, itemMapperField3};

        Set<pse.SObjectCloneMapper.Field> setMaterialMapperFields =
            new Set<pse.SObjectCloneMapper.Field>{materialMapperField1, materialMapperField2, 
            materialMapperField3, materialMapperField4, materialMapperField5, materialMapperField6};

        Set<pse.SObjectCloneMapper.Field> setGroupMapperFields = 
            new Set<pse.SObjectCLoneMapper.Field>{groupMapperfield1};
 
        //Instantiate the SObjectCloneMapper using the prepared set of SObjectCloneMapper.Field for each object.
        pse.SObjectCloneMapper itemCloneMap = new 
            pse.SObjectCloneMapper(Checklist_Item__c.SObjectType, setItemMapperFields);
        pse.SObjectCloneMapper materialCloneMap = 
            new pse.SObjectCloneMapper(Material__c.SObjectType, setMaterialMapperFields);
        pse.SObjectCloneMapper groupCloneMap = 
            new pse.SObjectCloneMapper(Material_Group__c.SObjectType, setGroupMapperFields);


        //Instantiate the request
        pse.CreateProjectFromTemplateService.CreateProjectFromTemplateRequest projRequest =
            new pse.CreateProjectFromTemplateService.CreateProjectFromTemplateRequest(templateProjectId, 
            startDate);

        projRequest.ProjectName = projectName;
        projRequest.Mappers = new List<pse.SObjectCloneMapper> 
            { itemCloneMap, materialCloneMap, groupCloneMap };
        
        //Call the service to create the projects using the request list and store a list of responses.
        List<pse.CreateProjectFromTemplateService.CreateProjectResponse> projResponses = 
            pse.CreateProjectFromTemplateService.createProjectsFromTemplates( new 
            List<pse.CreateProjectFromTemplateService.CreateProjectFromTemplateRequest>{projRequest});
    }
}

So, what’s going on here? Why do Material Groups get copied but not Companies? That’s because we provided a Mapper for Material Group, but not for Company. If we had added a Mapper for Company too, then the CPFT Service would have copied companies for us. On the other hand, if we hadn’t provided the Material Group Mapper, then the CPFT wouldn’t copy Material Groups and the materials in the cloned project would look up to the same groups as the materials in the template.

And of course, the unit test:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@isTest
private class testCPFTInclChecklistItemsAndMaterials {
    @isTest static void test_method_one() {
        pse__Proj__c p = new pse__Proj__c();
        p.pse__Start_Date__c = Date.Today();
        p.Name = 'Test';
        p.pse__Is_Template__c = true;
        insert p;
		
        pse__Project_Phase__c phase = new pse__Project_Phase__c();
        phase.Name = 'Phase Test';
        phase.pse__Project__c = p.Id;
        insert phase;

        Checklist_Item__c ci = new Checklist_Item__c();
        ci.Name = 'Checklist Item Test';
        ci.Done__c = true;
        ci.Project_Phase__c = phase.Id;
        insert ci;

        fferpcore__Company__c c = new fferpcore__Company__c();
        c.Name = 'Test Company';
        insert c;

        Material_Group__c mg = new Material_Group__c();
        mg.Name = 'Test Group';
        insert mg;

        Material__c m = new Material__c();
        m.Name = 'Test Material';
        m.Project__c = p.Id;
        m.Quantity__c = 10;
        m.Unit_of_Measurement__c = 'ft';
        m.Company__c = c.Id;
        m.Material_Group__c = mg.Id;
        insert m;

        Date d = Date.Today();
        String s = 'Test Case Project';
        Test.StartTest();
        CPFTIncludingChecklistItemsAndMaterials.callCPFTIncludingChecklistItemsAndMaterials(p.Id, d, s);
        Test.StopTest();
		
        Checklist_Item__c checkItem = [SELECT Name, Done__c, Project_Phase__r.Name, 
        	Project_Phase__r.pse__Project__r.Name, Project_phase__r.pse__Project__r.pse__Start_Date__c 
              FROM Checklist_Item__c WHERE Project_Phase__r.pse__Project__r.Name ='Test Case Project' 
              LIMIT 1];

        
        //Check Checklist Item fields and successfully created Project and related fields
        System.assertEquals(false, checkItem.Done__c);
        System.assertEquals('Checklist Item Test', checkItem.Name);
        System.assertEquals('Phase Test',checkItem.Project_Phase__r.Name);
        System.assertEquals(s, checkItem.Project_Phase__r.pse__Project__r.Name);
        System.assertEquals(d, checkItem.Project_phase__r.pse__Project__r.pse__Start_Date__c);

        Material__c checkMaterial = [SELECT Name, Company__r.Name, Material_Group__r.Name, 
            Quantity__c, Unit_of_Measurement__c, Project__r.Name FROM Material__c WHERE 
            Project__r.Name = 'Test Case Project' LIMIT 1];

        //Check Material fields
        System.assertEquals('Test Company', checkMaterial.Company__r.Name);
        System.assertEquals('Test Group', checkMaterial.Material_Group__r.Name);
        System.assertEquals('Test Material', checkMaterial.Name);
        System.assertEquals(10, checkMaterial.Quantity__c);
        System.assertEquals('ft', checkMaterial.Unit_of_Measurement__c);
        System.assertEquals(s, checkMaterial.Project__r.Name);
    }
}