Apex Coding Challenge for Iterating Lists

Here is the problem statement:

Given an expectedSum value find the sum (n+ (n+1) = expectedValue) from a list that equals the expectedSum. I am using the following list

1,3,4,4,5,9

and looking for the sum to be 8 (3+5, 4+4)

*Note the list is sorted

Solution 1:


        Integer expectedSum = 8;
		List<Integer> listInts = Arrays.asList(1,3,4,4,5,9);
		
		for (Integer k=0; k<listInts.size();k++){
			for (Integer j=k+1; j<listInts.size();j++){
				if (listInts.get(k)+listInts.get(j)==expectedSum){
					System.debug(listInts.get(k) + ' + ' + listInts.get(j));
				}
			}
		}	

The above time complexity for a nested for loop is O(n^2)

Solution 2:

Do a binary search to search if the diff is contained in the list


		Integer expectedSum = 8;
		List<Integer> listInts = Arrays.asList(1,3,4,4,5,9);
		for (Integer listInt : listInts){ 
			Integer diffInt = expectedSum-listInt;
			if (binarySearch(listInts, diffInt)!=null){
				System.debug(listInt + " + " + diffInt);
			}
		}
	
	
	public static Integer binarySearch(List<Integer> listInts, Integer searchInt){
		Integer startPos = 0;
		Integer listSize = listInts.size() - 1;
		Integer mid;
		while(startPos <= listSize){
			mid=(startPos+listSize)/2;
			if(listInts.get(mid) == searchInt){
				 return listInts.get(mid);
			}     
			else if(searchInt < listInts.get(mid)){
				listSize = mid - 1;
			}    
			else{
				startPos = mid + 1;
			}
		}
		return null;	 
	}

the above time complexity for binary search is for each element in the array O(n log n) Returning null is not a good practice try to return another number or throw an exception.

Solution 3:

Check if the diff is contained in the list by using the .contains method


		Integer expectedSum = 8;
		List<Integer> listInts = Arrays.asList(1,3,4,4,5,9);

		for (Integer listInt : listInts){
			Integer diffInt =expectedSum-listInt;
			if (listInts.contains(diffInt)){
				System.debug(listInt + ' + ' + diffInt);
			}
		}

the above time complexity for loop is O(n)

Option 4

Start on either end of the array and move inwards when you find a solution, if the sum of the outer and inner element is bigger that the expectedSum move the maxPointer inwards, if it is smaller move the minPointer inwards.

        Integer expectedSum = 1;
		List<Integer> listInts = Arrays.asList(1,3,4,4,5,9);

		Integer maxPointer = listInts.size()-1;
		Integer minPointer = 0;
		for (Integer k=0; k<listInts.size();k++){
			if (expectedSum < listInts.get(maxPointer)){
				maxPointer-=1;
			} else if (minPointer!=maxPointer){
				Integer sumPair =listInts.get(minPointer) + listInts.get(maxPointer);
				if ( sumPair == expectedSum){
					System.debug(listInts.get(minPointer) + " + " + listInts.get(maxPointer));
					minPointer +=1;
					maxPointer-=1;
				} else if (sumPair < expectedSum){
					minPointer +=1;
				} else {
					maxPointer-=1;;
				}
			}
			
		}

the above time complexity for loop is O(n)

Output:

4 + 4
5 + 3
3 + 5

Bonus:

How to do it with an unsorted list:

Iterate through the list and add the integer to the list, if the diff of the expected sum and integer is contained in the set then print it

		Integer expectedSum = 8;
		List<Integer> listInts = Arrays.asList(7,4,6,1,5,2,3);

		Set<Integer> intSetWithDiff = new HashSet<>();
		for (Integer listInt : listInts){
			Integer sumDiff = expectedSum - listInt;
			
			if (intSetWithDiff.contains(sumDiff)){
				System.debug(listInt + " + " + sumDiff);
			}
			intSetWithDiff.add(listInt);		
		}

Output:

1 + 7
2 + 6
3 + 5

Apex Interview Question > FizzBuzz

Consider the following problem:

Write a short program that prints each number from 1 to 100 in one line

For each multiple of 3, print “Fizz” instead of the number.

For each multiple of 5, print “Buzz” instead of the number.

For numbers which are multiples of both 3 and 5, print “FizzBuzz” instead of the number.
Write a solution (or reduce an existing one) so it has as few characters as possible.

Solution 1: For loop with multiple if statements

String str = '';
for(Integer i = 1; i&amp;lt;=100; i++){
    if(Math.mod(i,3)==0)
        str+='Fizz';
    if(Math.mod(i,5)==0)
        str+='Buzz';
    else if(Math.mod(i, 3) != 0)
		str+=i;
    str +=',';
}
System.debug(str);

Output

1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,16,17,Fizz,19,
Buzz,Fizz,22,23,Fizz,Buzz,26,Fizz,28,29,FizzBuzz,31,32,Fizz,34,Buzz,
Fizz,37,38,Fizz,Buzz,41,Fizz,43,44,FizzBuzz,46,47,Fizz,49,Buzz,
Fizz,52,53,Fizz,Buzz,56,Fizz,58,59,FizzBuzz,61,62,Fizz,64,Buzz,
Fizz,67,68,Fizz,Buzz,71,Fizz,73,74,FizzBuzz,76,77,Fizz,79,Buzz,
Fizz,82,83,Fizz,Buzz,86,Fizz,88,89,FizzBuzz,91,92,Fizz,94,Buzz,
Fizz,97,98,Fizz,Buzz

Apex FeedItem Trigger Share to Community

When uploading a new FeedItem we want to share it to a specific community. First thing we need to do is share the FeedItem to a community by sharing it to the User. As the documentation states: Only feed items with a Group or User parent can set a NetworkId or a null value for NetworkScope.

https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_feeditem.htm

NetworkId—The ID of the community in which the FeedItem is available. If left empty, the feed item is only available in the default community.

FeedItemTrigger Trigger

trigger FeedItemTrigger on FeedItem (after insert, before insert) {
    fflib_SObjectDomain.triggerHandler(App_Domain_FeedItem.class);
}

FeedItemTrigger Domain Class

public class App_Domain_FeedItem extends fflib_SObjectDomain {

	public override void onBeforeInsert(){
		List<ContentDocumentLink> contentDocumentLinksList = new List<ContentDocumentLink>();
		for(FeedItem record : (List<FeedItem>) Records){
			record.Body = record.ParentId;
			record.NetworkScope = {NetworkId};
			record.ParentId = UserInfo.getUserId();
			record.Visibility='AllUsers';
		}
	}
}

Now that the file is shared with the user we can give View sharing back to the original object using ContentDocumentLink by using the RelatedRecordId

public class App_Domain_FeedItem extends fflib_SObjectDomain {
public override void onAfterInsert(){
		List<ContentDocumentLink> contentDocumentLinksList = new List<ContentDocumentLink>();
		List<Documents__c> bpDocumentsList = new List<Documents__c>();

		Map<Id, Id> feedItemToRelatedRecordIdMap = new Map<Id, Id>();
		for(FeedItem record : (List<FeedItem>) Records){
			feedItemToRelatedRecordIdMap.put(record.Id, record.RelatedRecordId);
		}

		Map<Id, Id> mapOfRelatedRecordContentVersionMap = new Map<Id, Id>();
		List<ContentVersion> contentDocumentVersions = [Select Id, ContentDocumentId from ContentVersion where id IN :feedItemToRelatedRecordIdMap.values()];
		for (ContentVersion contentDocumentVersion : contentDocumentVersions){
			mapOfRelatedRecordContentVersionMap.put(contentDocumentVersion.Id, contentDocumentVersion.ContentDocumentId);
		}

		for(FeedItem record : (List<FeedItem>) Records){
			String bpDocumentId = record.Body;

			Id relatedRecordId = feedItemToRelatedRecordIdMap.get(record.Id);
			Id contentDocumentId = null;
			if (mapOfRelatedRecordContentVersionMap.containsKey(relatedRecordId)){
				 contentDocumentId = mapOfRelatedRecordContentVersionMap.get(relatedRecordId);
			}

			if (contentDocumentId!=null){
				ContentDocumentLink cdl = new ContentDocumentLink(LinkedEntityId = bpDocumentId , ContentDocumentId=contentDocumentId	, shareType = 'V');
				contentDocumentLinksList.add(cdl);
			}

			Documents__c updateDocument = new Documents__c(Id=bpDocumentId);
			updateDocument.Document_Status__c='Downloaded';
			String formatDownloadUrl = '/CommunityApi/sfc/servlet.shepherd/version/download/{0}?asPdf=false&operationContext=CHATTER';
			updateDocument.Download_Url__c = String.format(formatDownloadUrl, new List<String>{contentDocumentId});
			updateDocument.File_Id__c = contentDocumentId;
			updateDocument.File_Name__c = record.Title;
			updateDocument.File_Size__c = record.ContentSize;
			updateDocument.File_Type__c = record.ContentType!=null ? mimeTypeMap.containsKey(record.ContentType.toLowerCase()) ? mimeTypeMap.get(record.ContentType.toLowerCase()) : record.ContentType  : record.ContentType;
			updateDocument.File_Permission__c='R';

			bpDocumentsList.add(updateDocument);
		}

		if (!contentDocumentLinksList.isEmpty()){
			insert contentDocumentLinksList;
		}

		if (!bpDocumentsList.isEmpty()){
			update bpDocumentsList;
		}
	}
}

Now the Feeditem is shared both to the user and the object. It can be downloaded by the logged in user.

Apex Test Active Case Assignment Rules Assertion Fails

If you are testing active assignment rules you need to add it as an DMLOptions during testing else your assignment rule(s) will not get called and your assertion(s) may fail.

If you have one active Case Assignment Rule you can use dmlOpts.assignmentRuleHeader.useDefaultRule = true else you need to query for AssignmentRules. To query AssignmentRules you will need to have without sharing set as your class level access, else you will not get access to AssignmentRules.

If you have one method to insertCases you can make sure your assignmentRules run every time you execute a test class.

  public Id insertCase(Case bpCase){
      fflib_ISObjectUnitOfWork uow = Rest_App.UnitOfWork.newInstance();

      if (Test.isRunningTest()){
        Database.DMLOptions dmlOpts = new Database.DMLOptions();
         dmlOpts.assignmentRuleHeader.useDefaultRule = true;
         bpCase.SetOptions(dmlOpts);
      }

      uow.registerNew(bpCase);
      uow.commitWork();
      return bpCase.Id;
}

Here is the actual test class:

	@isTest(SeeAllData=true) static void testClientRequest(){
		RestRequest req = new RestRequest();
		RestResponse res = new RestResponse();
		Map<String, String> mapOfIds = App_Global_Test.setupCommunityUserReturnIds();

		test.startTest();
			System.runAs(new User(Id=mapOfIds.get('UserId'))){
				req.requestBody = Blob.valueOf('{"contactId":"'+mapOfIds.get('ContactId')+'", "type":"Client Request", "description":"Please help me with my account", "subject":"Client Request", "reason":"Client Request", "origin":"App"}');
				req.requestURI = '/v1/support/case';
				req.httpMethod = 'POST';
				RestContext.request = req;
				RestContext.response = res;

				App_Rest_Dispatcher.doPost();
				System.assert(res.responseBody!=null);
				System.assertEquals(res.statusCode, 200);
		 }
		test.stopTest();

		List<Case> supportCase = [Select Id, Subject, Description, Type, Owner.Name from Case where ContactId=:mapOfIds.get('ContactId')];
		System.assertEquals(supportCase.size(), 1);
		System.assertEquals(supportCase.get(0).Subject, 'Client Request');
		System.assertEquals(supportCase.get(0).Type, 'Client Request');
		System.assertEquals(supportCase.get(0).Owner.Name, 'Client Services Queue');
	}

Apex Run multiple batch jobs sequentially

We can run 3 batch jobs sequentially by incrementing the jobCounter and passing the integer (job index) into the batch scope

This can be increased to any amount of batch jobs, the problem I solved was able to update the Contact and disable Users in the same code running as different batch job.

As you cannot call @future in a batch method this solves by running each update in their own batch = transaction 👍🏻

global with sharing class App_Job_Account_Delete implements System.Schedulable, Database.Batchable<Integer>, Database.Stateful, Database.AllowsCallouts  {

    private List<JobError> jobErrors = new List<JobError>();

    global Integer jobCounter = 1;

    public void execute(SchedulableContext sc) {
        Database.executeBatch(this, jobCounter);
    }

    public Integer[] start(Database.BatchableContext context) {
        return new Integer[] {jobCounter};
    }

    public void execute(Database.BatchableContext context, Integer[] scope)     {
        try{
            if (isSandbox() || Test.IsRunningTest()){
                if(scope[0] == 1) {
                    //add your code here to run as part of 1st batch
                } else if (scope[0] == 2){
                    //add your code here to run as part of 2nd batch
                }  else if (scope[0] == 3){
                    //add your code here to run as part of 3rd batch
                }
            } else {
                JobError jobError = new JobError();
                jobError.message = 'Environment Error: Job will only run on dev and automation environment';
                jobError.records = new List<SObject>();
                jobErrors.add(jobError);
            }
        } catch (Exception ex){
            JobError jobError = new JobError();
            jobError.records = new List<SObject>();
            jobError.message = 'Exception: ' + ex.getTypeName() + ': ' + ex.getMessage()  + ' -- ' + ex.getCause();
            jobErrors.add(jobError);
        }
    }

    public void finish(Database.BatchableContext context){
        if (jobCounter<3){
            jobCounter++;
            Database.executeBatch(this, jobCounter);
        }
    }

    public class JobError{
        public String message;
        public List<SObject> records;
    }

    public void setJobError(List<JobError> jobErrors){
        this.jobErrors = jobErrors;
    }

    public static Boolean isSandbox() {
        if ([SELECT IsSandbox FROM Organization LIMIT 1].IsSandbox && ('Dev'.equalsIgnoreCase(CustomSettings.Environment__c) || 'Automation'.equalsIgnoreCase(CustomSettings.Environment__c))){
            return true;
        } else {
            return false;
        }
    }
}

Run the code as

Id batchprocessid = Database.executeBatch(new App_Job_Account_Delete());

Apex Comparator compare multiple object fields

Sorting a list of Analysis messages first by boolean and then integer. First we will order by condition and then order. All records where condition is true is will be on top in descending order on top followed by all false condition in descending order.

SummaryAnalysisMessages object to sort

public class SummaryAnalysisMessages {
		private String title;
		private String description;
		private Integer order;
		private Boolean condition;

		public SummaryAnalysisMessages(String title, String description, Integer order, Boolean condition){
			this.title = title;
			this.description = description;
			this.order = order;
			this.condition = condition;
		}

		public Boolean getCondition(){
			return condition;
		}

		public Integer getOrder(){
			return order;
		}
	}

Compare object by condition and then order

public class SummaryAnalysisMessagesCompare extends App_Comparator {
    public override Integer compare(Object a, Object b) {
			SummaryAnalysisMessages aSummaryMessage = (SummaryAnalysisMessages)a;
			SummaryAnalysisMessages bSummaryMessage = (SummaryAnalysisMessages)b;

      Integer summaryMessage1 = aSummaryMessage.getCondition() ? 1 : 0;
      Integer summaryMessage2 = bSummaryMessage.getCondition() ? 1 : 0;

			Integer compareInt = summaryMessage2 - summaryMessage1;
      if (compareInt == 0) {
          compareInt = aSummaryMessage.getOrder() - bSummaryMessage.getOrder();
      }
      return compareInt;
    }
}

Test class to test order

@isTest static void testSummaryAnalysisMessagesCompare(){
		SummaryAnalysisMessages summaryAnalysisMessage1 = new App_Chart.SummaryAnalysisMessages('1', '', 1, false);
		SummaryAnalysisMessages summaryAnalysisMessage2 = new App_Chart.SummaryAnalysisMessages('2', '', 2, true);
		SummaryAnalysisMessages summaryAnalysisMessage3 = new App_Chart.SummaryAnalysisMessages('3', '', 3, false);
		SummaryAnalysisMessages summaryAnalysisMessage4 = new App_Chart.SummaryAnalysisMessages('4', '', 4, true);

		List<SummaryAnalysisMessages> assetAllocationSummaryList = new List<SummaryAnalysisMessages>{summaryAnalysisMessage1, summaryAnalysisMessage2, summaryAnalysisMessage3, summaryAnalysisMessage4};

		App_Comparator.sort(assetAllocationSummaryList, new App_Chart.SummaryAnalysisMessagesCompare());
		System.assertEquals(assetAllocationSummaryList.get(0).getOrder(), 2);
		System.assertEquals(assetAllocationSummaryList.get(1).getOrder(), 4);
		System.assertEquals(assetAllocationSummaryList.get(2).getOrder(), 1);
		System.assertEquals(assetAllocationSummaryList.get(3).getOrder(), 3);
	}

Apex save class when scheduled > ‘This schedulable class has jobs pending or in progress’

How do I develop in a class that is referenced in a scheduled job?You will see the following error when you try to save this class:

This schedulable class has jobs pending or in progress

There is the workaround, what I did is create a Job scheduler so basically 1 scheduled class from where I reference all my scheduled classes. We are not initializing the class by creating a object of the class by name:

global class App_Job_Scheduler implements Schedulable {

	global void execute(SchedulableContext sc) {
		System.Type closedAccountsScheduler = Type.forName('Job_Closed_Accounts');
		Schedulable closedAccountObject = (Schedulable)closedAccountsScheduler.newInstance();
		closedAccountObject.execute(sc);
       }
}

Scheduler class, now you can schedule this class and will not get the >This schedulable class has jobs pending or in progress error when you try to save any code to App_Service or Selector_Financial_Account

global class Job_Closed_Accounts implements System.Schedulable, Database.Batchable<SObject>, Database.Stateful, Database.AllowsCallouts {

	global Database.QueryLocator start(Database.BatchableContext BC) {
		return App_Selector_Financial_Account.newInstance().selectAccountAndRelatedObjectsToDelete();
	}

    global void execute(Database.BatchableContext BC, List<Financial_Account__c> financialAccountIdsToBeDeleted) {
		List<Financial_Account__c> financialAccountsToDelete = App_Selector_Financial_Account.newInstance().selectAccountAndRelatedObjectsToDelete(financialAccountIdsToBeDeleted);
		App_Service.instance.deleteRelatedAccountsRecords(financialAccountsToDelete);
	}

	global void finish(Database.BatchableContext BC) {}
}

Generating an Email-to-Case thread id using Apex

How to generate Email-to-Case thread id inside template:

Generate the thread Id

public static String getThreadId(String caseId){
public static String getThreadId(String caseId){
  return '[ ref:_'
     + UserInfo.getOrganizationId().left(5)
     + UserInfo.getOrganizationId().mid(11,4) + '._'
     + caseId.left(5)
     + caseId.mid(10,5) + ':ref ]';
}

Send customEmail and Add ThreadId

public void sendCustomEmail(User selectedUser, Contact selectedContact, String templateName, Map<String, String> customVariables){
    EmailTemplate emailTemplate = [select Id, Subject, HtmlValue, Body from EmailTemplate where DeveloperName=:templateName];

    if (selectedContact==null){
       selectedContact =  selectedUser.Contact;
    }

    String customSubject = emailTemplate.Subject;

    if (customVariables.containsKey('joinContactName')){
      customSubject = customSubject.replace('{!JointContactName}', customVariables.get('joinContactName'));
    }
    //Add ThreadId to the Subject
    if (customVariables.containsKey('ThreadId')) {
      customSubject = customSubject.replace('{!ThreadId}', customVariables.get('ThreadId'));
    }

    String htmlBody = emailTemplate.HtmlValue;
    htmlBody = htmlBody.replace('{!Contact.FirstName}',selectedContact.FirstName);
    htmlBody = htmlBody.replace('{!Contact.Email}', selectedContact.Email);
    if (customVariables.containsKey('Link')){
      htmlBody = htmlBody.replace('{!CustomUrl}', customVariables.get('Link'));
    } else if (customVariables.containsKey('link')){
      htmlBody = htmlBody.replace('{!CustomUrl}', customVariables.get('link'));
    } else if (customVariables.containsKey('joinContactName')){
      htmlBody = htmlBody.replace('{!JointContactName}', customVariables.get('joinContactName'));
    }

    //Add ThreadId to the htmlBody
    if (customVariables.containsKey('ThreadId')){
      htmlBody = htmlBody.replace('{!ThreadId}', customVariables.get('ThreadId'));
    }

    String plainBody = emailTemplate.Body;
    plainBody = plainBody.replace('{!Contact.FirstName}',selectedContact.FirstName);
    plainBody = plainBody.replace('{!Contact.Email}', selectedContact.Email);
    if (customVariables.containsKey('Link')){
      plainBody = plainBody.replace('{!CustomUrl}', customVariables.get('Link'));
    } else if (customVariables.containsKey('link')){
      plainBody = plainBody.replace('{!CustomUrl}', customVariables.get('link'));
    } else if (customVariables.containsKey('joinContactName')){
      plainBody = plainBody.replace('{!JointContactName}', customVariables.get('joinContactName'));
    }

    //Add the threadId to the plainBody
    if (customVariables.containsKey('ThreadId')){
      plainBody = plainBody.replace('{!ThreadId}', customVariables.get('ThreadId'));
    }

    Messaging.Singleemailmessage email = new Messaging.Singleemailmessage();
    List<OrgWideEmailAddress> orgWideEmailAddress = [Select Id, Address from OrgWideEmailAddress where DisplayName=:ORGWIDEEMAILADDRESSNAME];
    //Set ReplyTo as the Email-to-Case email address
    if (customVariables.containsKey('ThreadId')){
      List<EmailServicesAddress> emailServicesAddress = [SELECT Id,AuthorizedSenders,EmailDomainName,IsActive,LocalPart FROM EmailServicesAddress where IsActive=true and (LocalPart like '%service%' or LocalPart like '%support%')];
      if (!emailServicesAddress.isEmpty()){
        EmailServicesAddress emailToCase = emailServicesAddress.get(0);
        email.setReplyTo(emailToCase.LocalPart +'@'+ emailToCase.EmailDomainName);
        email.setSenderDisplayName('Client Support');
      } else {
        email.setReplyTo('customer-service@gmail.com');
        email.setSenderDisplayName('Client Support');
      }
    }

    email.setTargetObjectId(selectedContact.Id);
    email.setSaveAsActivity(true);

    email.setSubject(customSubject);

    email.setHtmlBody(htmlBody);
    email.setPlainTextBody(plainBody);

    if (customVariables.containsKey('WhatId')){
      email.setWhatId(customVariables.get('WhatId'));
    }

    Messaging.sendEmail(new Messaging.SingleEmailmessage[] {email});
}

Putting it all together

Map<String, String> mappingOfCaseFields = new Map<String, String>();
mappingOfCaseFields.put('ThreadId', App_Service.getThreadId(paymentCaseId));
App_Service.instance.sendCustomEmail(null, contact, 'App_Support_Template', mappingOfCaseFields);

Apex callout PATCH to Heroku Controller workaround

Setup a method to send Aync call to Heroku, add ‘?_HttpMethod=PATCH’ to the path to notify Heroku it’s a patch request

@future(callout=true)
	public static void callApiEndpointAsync(String apiEndpoint, String method, String aPayload){
		HttpRequest req = new HttpRequest();
		List<String> theArgs = new List<String>();
		HttpResponse res = new HttpResponse();
    try {
      if (apiEndpoint != null) {
        req.setTimeout(120000);
				if ('PATCH'.equals(method)){
					apiEndpoint += '?_HttpMethod=PATCH';
					req.setMethod('POST');
				} else {
					req.setMethod(method);
				}

        setAuthHeaderAsync(req);
        req.setEndpoint(herokuUrl + apiEndpoint);
				if (aPayload!=null)
					req.setBody(String.valueOf(aPayload));

				if (Test.isRunningTest() && (mock!=null)) {
					 mock.respond(req);
			 	} else {
					Integer retry = 0;

					while (retry < 2){
						Http http = new Http();
		        res =  http.send(req);
						if (res.getStatusCode() >= 200 && res.getStatusCode() < 300) {
							retry +=2;
						} else if (res.getStatusCode()==503 || res.getStatusCode()==400){
							retry +=1;
							res = http.send(req);
						} else {
							retry +=1;
							theArgs.add('Api');
							theArgs.add(req.getEndpoint());
							theArgs.add(res.getBody());
						}
					}
					throw new Rest_Exception(ResponseCodes_Mgr.getCode('HEROKU_REQUEST_CALLOUT_FAILED', null, theArgs));
				}

      } else {
        System.debug('Service apiEndpoint and payload must not be null');
        throw new Rest_Exception(ResponseCodes_Mgr.getCode('HEROKU_REQUEST_CALLOUT_FAILED'));
      }
    } catch (Exception ex) {
			theArgs.add('Api');
			theArgs.add(req.getEndpoint());
			theArgs.add(res.getBody());
			throw new Rest_Exception(ResponseCodes_Mgr.getCode('HEROKU_REQUEST_CALLOUT_FAILED', ex, theArgs));
			
    }
}

Spring controller checks the RequestParam _HttpMethod to see if it’s a POST or PATCH request

@RequestMapping(value = "/customer", method = RequestMethod.POST, produces = "application/json")
@ResponseBody
@ApiOperation(value = "Creates new payment customer")
@ApiResponses(value = {@ApiResponse(code = 200, message = "Creates new payment customer")})
public String createCustomer(@RequestBody PaymentCustomerWrapper paymentCustomerWrapper, @RequestParam(value="_HttpMethod", defaultValue="POST")  String httpMethod)
        throws IOException, URISyntaxException {
     if (httpMethod!=null && "PATCH".equals(httpMethod)){
         itsLogger.debug("update payment customer {}", paymentCustomerWrapper.toString());
         return paymentService.updatePaymentCustomer(paymentCustomerWrapper).toJson();
    } else {
         itsLogger.debug("create payment customer {}", paymentCustomerWrapper.toString());
         return paymentService.createChargeBeeCustomer(paymentCustomerWrapper).toJson();
    }
}

Apex Return Diff fields between records

Sometimes you want to determine what has changed between an existing record in the database and an update to the record. The diffRecord method will return all the fields that changed for a specific record.

Compare the difference between to records and return changed fields

public SObject diffRecord(SObject updatedRecord, SObject currentRecord, String sObjectName){
  SObjectType objToken = Schema.getGlobalDescribe().get(sObjectName);
  DescribeSObjectResult objDef = objToken.getDescribe();
  Map<String, SObjectField> fields = objDef.fields.getMap();
  Type classType = Type.forName(sObjectName);
  SObject diffRecord = (SObject)JSON.deserialize('{}', classType);
  for (String field : fields.keySet()){
    if (!fieldsToExclude.contains(fields.get(field).getDescribe().getName()) && (fields.get(field).getDescribe().isUpdateable() || fields.get(field).getDescribe().isCreateable())){
        if (updatedRecord.get(field)!=currentRecord.get(field))
        	diffRecord.put(field, updatedRecord.get(field));
    }
  }
  return diffRecord;
}

Test that only changed fields are returned

@isTest static void testDiffRecords(){
  String currentRecordJSON = '{"Id":"a0L63000000qc43EAA","Account_Type__c":"Managed","Amount__c":50.00,"Category__c":"Deposit","Description__c":"Deposit","End_Date__c":"2020-03-31T15:06:57.000+0000","Frequency__c":"Monthly"}';
  String updatedRecordJSON = '{"Amount__c":50.0,"Id":"a0L63000000qc43EAA","Frequency__c":"Annually"}';
  Type classType = Type.forName('Account');
  SObject currentRecord = (SObject)JSON.deserialize(currentRecordJSON, classType);
  SObject updatedRecord = (SObject)JSON.deserialize(updatedRecordJSON, classType);
  Test.startTest();
    SObject diffRecord = App_Service.instance.diffRecord(updatedRecord, currentRecord, 'Account');
    System.assertEquals(diffRecord.get('Amount__c'), null);
    System.assertEquals(diffRecord.get('Frequency__c'), 'Annually');
    System.assertEquals(diffRecord.get('Account_Type__c'), null);
    System.assertEquals(diffRecord.get('Category__c'), null);
  Test.stopTest();
}