When sign-ups are recorded in Salesforce (in the Volunteer Hours object), that data is pushed to the V4S Kiosk and V4S Personal mobile apps via a trigger that runs on the Volunteer Hours object. With web sign-ups, there's a bit of a complication. Web sign-ups are written into Salesforce via the Guest Site User. If that user does not have a valid V4S Mobile license, Salesforce will not run the trigger that sends sign-ups to the mobile apps. So such sign-ups are not displayed in the mobile apps. Of late, there's a further complication. Salesforce's new sharing rules (re: Parent Sharing) dictate that the trigger will only run if the user inserting the data not only has a V4S Mobile license but also has access to the Contact, Job, and Shift objects.


One way to solve this issue is to do the following:

  1. Assign V4S Mobile licenses to all users who could create Volunteer Hours, including the Guest Site User who creates web-sign-up records.
  2. Configure Guest Site User Access for Volunteers, as described here in the Salesforce documentation.
  3. Give access to Contacts, Jobs, Shifts, and Volunteer Hours to all the users in item 1 above.


Alternatively, you could add a trigger that sends the info to the mobile apps regardless of who the Volunteer Hour record was created by. You can do that using the sample code shown below, into Salesforce. This is an advanced Salesforce feature and is best done by someone who has a good understanding of Salesforce Apex, triggers, and controllers, among other things.


Please note: The code below is provided with no warranties or support commitments from PK4. We suggest you understand how it works and be prepared to make changes as needed, based on the configuration of your own Salesforce instance.


In order to make this happen, you need to add two Apex classes, one Apex trigger, and an Apex Test Class. Here are the details. Please make sure you follow the 4 steps below in the sequence listed. If you have trouble at any stage, please consult the relevant Salesforce documentation for help.


Please add the below Global Class, Public Class, & Trigger using your Sandbox org, test the app in Sandbox and then push the same to your Production org.


Step 1: Add a Global Class

  • Log in to Salesforce as Administrator and click the setup button, search for Apex Classes in the Quick Find box, and click it
  • Click the New button, add the below details in the New Apex Class and click the Save button


global class SFtodftlyFeatureClass {
 @future(callout = true)
 global static void postTTService(String Url, String reqPacket) {
   SFtodftlyFeatureClass cl_out = new SFtodftlyFeatureClass();
   if (!System.Test.isRunningTest()) {
     HttpResponse res = cl_out.sendTTServiceRequest(Url, reqPacket);
   }
 }
 public HttpResponse sendTTServiceRequest(String Url, String reqPacket) {
   HttpRequest req = new HttpRequest();
   req.setEndpoint(Url);
   req.setMethod('POST');
   req.setTimeOut(120000);
   req.setBody(reqPacket);
   req.setHeader('content-type', 'application/json');
   Http http = new Http();
   HTTPResponse res = http.send(req);
   System.debug(res.getBody());
   return res;
 }
}

// end of Global class


Step 2: Add a Public Class with sharing class notifier


  • Once the first Apex  Class is saved, click the new button again in the Apex Class, add the below content, and Save it

public with sharing class StoreNotifier {

  // List<Object> FCM_objects = new List<Object>();

  public static void sendNotify(List < Object > records, String ObjName, String EventType) {

    String post_url = 'https://dftly.com/dftly/sfdc/apiv2/vfs/send_notify';

    JSONGenerator gen = JSON.createGenerator(true);

    gen.writeStartObject();

    gen.writeStringField('event_type', EventType);

    gen.writeStringField('obj_name', ObjName);

    gen.writeObjectField('records', records);

    gen.writeStringField('appkey', '********');

    gen.writeEndObject();

    String jsonS = gen.getAsString();

    SFtodftlyFeatureClass.postTTService(post_url, jsonS);

  }

}

//  end of Public class


Please make sure you key in your org's App Key in the section of the code that is marked with ******** above.


Step 3: Add the Trigger


Once both the Apex Classes are set, write the trigger below for updating the records


  • From Setup, click the Object Manager string, and search for the Volunteer Hours object, click the Triggers from the Volunteer Hours menu list
  • Click the New button, and you might see some text already on the trigger screen, clear the screen and then add the trigger as below and Save it.


trigger V4ShourNotifier on GW_Volunteers__Volunteer_Hours__c(after update, after insert, after delete) {
    try {
        List < GW_Volunteers__Volunteer_Hours__c > Obj_list_delete = new List < GW_Volunteers__Volunteer_
Hours__c > ();
        if (Trigger.isAfter) {
            // // This takes all available fields from the required object.            
            // To get List of History tracking fields of Project
            List < Object > Obj_list_update = new List < Object > ();
            List < Object > Obj_list_insert = new List < Object > ();
            //Map<String, Schema.SObjectField> mapFields = Schema.SObjectType.GW_Volunteers__Volunteer_Hours__c.fields.getMap();
            if (trigger.isDelete) {
                for (GW_Volunteers__Volunteer_Hours__c proj_fcm: trigger.old) {
                    Obj_list_delete.add(proj_fcm);
                }
                StoreNotifier.sendNotify(Obj_list_delete, 'GW_Volunteers__Volunteer_Hours__c', 'delete');
            } else {
                for (GW_Volunteers__Volunteer_Hours__c proj: trigger.new) {
                    if (trigger.isUpdate) {
                        Obj_list_update.add(proj);
                    }
                    // on Create/Insert
                    if (trigger.isInsert) {
                        Obj_list_insert.add(proj);
                    }
                }
                // Submit to PK4 API
                if (Obj_list_update != null && !Obj_list_update.isEmpty()) {
                    StoreNotifier.sendNotify(Obj_list_update, 'GW_Volunteers__Volunteer_Hours__c', 'update');
                }

                if (Obj_list_insert != null && !Obj_list_insert.isEmpty()) {
                    StoreNotifier.sendNotify(Obj_list_insert, 'GW_Volunteers__Volunteer_Hours__c', 'insert');
                }
            }
        }
    } catch(System.CalloutException e) {
        System.debug('ERROR:' + e);
    }
}

//  ens of trigger code


Step 4: Add the Test Class

  • Click the setup button, search for Apex Classes in the Quick Find box, and click it
  • Click the New button add the below details in the New Apex Class and click the Save button


@isTest
private class V4SFeatureClassTest implements HttpCalloutMock {

    private HttpResponse resp;
    static GW_Volunteers__Volunteer_Hours__c vol_hours1;
    static GW_Volunteers__Volunteer_Shift__c vol_Shift1;
    static GW_Volunteers__Volunteer_Job__c vol_Job1;

    public V4SFeatureClassTest (String testBody) {
        resp = new HttpResponse();
        resp.setBody(testBody);
        resp.setStatusCode(200);
    }

    public HTTPResponse respond(HTTPRequest req) {
        System.assertEquals(200, resp.getStatusCode());
        return resp;
    }

    public testmethod static void createVHours() {
        vfs__dftly_VFS_App__c dac = new vfs__dftly_VFS_App__c (Name = 'V4S APP',vfs__App_Description__c = 'Test Volunteers');
        insert dac;
        Contact time_Cont1 =  new Contact(FirstName = 'Time', LastName = 'time_Cont1');
        insert time_Cont1;
        Campaign camp1 = new Campaign(Name = 'Test Campaign 1', IsActive = True);
        insert camp1;

        vol_Job1 = new GW_Volunteers__Volunteer_Job__c( name = 'Vol_Job1', GW_Volunteers__Campaign__c = camp1.Id );
        insert vol_Job1;

        vol_Shift1 = new GW_Volunteers__Volunteer_Shift__c (GW_Volunteers__Volunteer_Job__c = vol_Job1.Id , GW_Volunteers__Start_Date_Time__c = system.today(), GW_Volunteers__Duration__c = 2.00);
        insert vol_Shift1;

        vol_hours1 = new GW_Volunteers__Volunteer_Hours__c (GW_Volunteers__Contact__c = time_Cont1.Id, GW_Volunteers__Start_Date__c = system.today(), GW_Volunteers__Volunteer_Job__c = vol_Job1.Id, GW_Volunteers__Volunteer_Shift__c = vol_Shift1.Id , GW_Volunteers__Status__c = 'Confirmed', GW_Volunteers__Number_of_Volunteers__c = 1);

        insert vol_hours1;

        // Update a vol_hour record

        vol_hours1.GW_Volunteers__Number_of_Volunteers__c = 2;
        update vol_hours1;

        // delete a vol_hour record
        delete vol_hours1;  

    }

    public testmethod static void test_WS_vfsFeatureClass() {
        String testBody = 'This is a test :-)';
        V4SFeatureClassTest mock = new V4SFeatureClassTest(testBody);
        Test.setMock(HttpCalloutMock.class, mock);
        SFtodftlyFeatureClass callout = new SFtodftlyFeatureClass();
        Test.startTest();
            HttpResponse resp = callout.sendTTServiceRequest('appkey','name');
        Test.stopTest();    
        System.assertEquals(200, resp.getStatusCode());        
        System.assertEquals(resp.getBody(), testBody );
    }

}

//  end of Test Class


*****