Field History Tracking in Replication Models
The following table describes how the field history tracking works in different replication models:
Please make your solution decisions based on generally available functionality only.
Functionality | Legacy replicated model (pre-commit/post-commit) | UI-based replicated model (custom Lightning Web Components) |
---|---|---|
Create/update a record from UI | The Field History saves a regulated data value before the pre-commit process:
In this scenario, the Field History tracks a regulated data value BEFORE its synchronization with the InCountry platform. | The Field History saves a regulated data value after its synchronization with the InCountry platform:
In this scenario, the Filed History tracks a regulated data value AFTER its synchronization with the InCountry platform. |
Create/update a record via custom Salesforce API | Depends on the architecture of a custom Apex webservice. The following option is available (with Apex SDK):
In this scenario, the Field History is recorded after its synchronization with the InCountry platform. | |
Create/update a record via the native Salesforce API | In this scenario the Field History always tracks a regulated data value BEFORE its synchronization with the InCountry platform. As a workaround, you can use InbCountry Border. In this case, Border will capture regulated values and will store them in InCountry. The Field History will capture the regulated data value AFTER its synchronization with the InCountry platform. | |
Update a regulated field in BEFORE triggers | The Field History tracks a regulated data value before its synchronization with the InCountry platform. Two potential workarounds are available:
| |
Change a regulated field in custom Apex controllers | Depends on the architecture of Apex controllers. The following option is available (with Apex SDK):
In this scenario, the Field History tracks a regulated data value after its synchronization with the InCountry platform. |
Code examples
Cases are created or updated via a custom Apex RESTful web-service
@RestResource(urlMapping='/Cases/*')
global with sharing class CaseWebService {
@HttpPut
global static Case upsertCase(String Subject, String Status, String Origin, String Priority, String Id) {
// Prepare Case record in memory
Case thisCase = new Case(
Subject=subject,
Status=status,
Origin=origin,
Priority=priority);
if (String.isNotBlank(Id)) {
thisCase.Id = Id;
}
// First save the case to InCountry
InCountryRecordSynchronizer synchronizer = new InCountryRecordSynchronizer('Case');
String temporaryId = synchronizer.addSObject(thisCase);
System.debug('temporary id = ' + temporaryId);
Map<String, InCountryRecordSynchronizer.InCountrySaveStatus> statuses = synchronizer.sync();
if (statuses.get(temporaryId).isSuccess) {
InCountryReplicationTriggerHandler.disableTrigger = true;
// Then upsert the case in Salesforce
upsert thisCase;
// If necessary change the case Id in InCountry
String recordId = String.valueOf(thisCase.get('Id'));
if (recordId != temporaryId) {
InCountryRecordSynchronizer.migrate(JSON.serialize(synchronizer.mapSensitiveData), new Map<String, String> {
temporaryId => recordId
});
}
}
// Return the case.
return thisCase;
}
}
A request is performed at this endpoint /services/apexrest/Cases/
{
"Status" : "Working",
"Subject" : "Bigfoot Sighting!",
"Priority" : "Medium"
}
The following result is returned:
{
"attributes" : {
"type" : "Case",
"url" : "/services/data/v53.0/sobjects/Case/5005w00001r5XvzAAE"
},
"Subject" : "Bigfoot Sighting!",
"Status" : "Working",
"Origin" : null,
"Priority" : "Medium",
"Id" : "5005w00001r5XvzAAE"
}
Regulated fields are updated in Case Trigger
Case - regulated object. Regulated fields are Type and Priority. A Customer wants to update Priority to high if Type is set to Structural.
Apex trigger:
trigger CaseTrigger on Case (after insert) {
if (Trigger.isAfter && Trigger.isInsert) {
List<Case> casesToUpdate = CaseTriggerHandler.getCasesToUpdate(Trigger.New);
if (!casesToUpdate.isEmpty()) {
CaseTriggerHandler.updateRegulatedFields((new Map<Id, Case>(casesToUpdate)).keySet());
}
}
}
Trigger Handler:
public class CaseTriggerHandler {
/*
* Asynchronous method with business logic and synchronization to InCountry PoP
*/
@future(callout=true)
public static void updateRegulatedFields(Set<Id> caseIds) {
List<Case> cases = [SELECT Id, Type, Priority FROM Case WHERE Id IN :caseIds];
List<Case> casesToUpdate = getCasesToUpdate(cases);
for (Case caseRecord : casesToUpdate) {
caseRecord.Priority = 'High';
}
if (!casesToUpdate.isEmpty()) {
InCountry service = InCountry.getInstance();
service.write(casesToUpdate);
}
update casesToUpdate;
}
public static List<Case> getCasesToUpdate(List<Case> cases) {
List<Case> casesToUpdate = new List<Case>();
for(Case caseRecord : cases) {
// Type and Priority are regulated fields
if (caseRecord.Type == 'Structural') {
casesToUpdate.add(caseRecord);
}
}
return casesToUpdate;
}
}
Cases are created via Apex Lightning Controller
public with sharing class CasesConroller {
@AuraEnabled
public static Case upsertCase(String Subject, String Status, String Origin, String Priority, String Id) {
// Prepare Case record in memory
Case thisCase = new Case(
Subject=subject,
Status=status,
Origin=origin,
Priority=priority);
if (String.isNotBlank(Id)) {
thisCase.Id = Id;
}
// First save the case to InCountry
InCountryRecordSynchronizer synchronizer = new InCountryRecordSynchronizer('Case');
String temporaryId = synchronizer.addSObject(thisCase);
System.debug('temporary id = ' + temporaryId);
Map<String, InCountryRecordSynchronizer.InCountrySaveStatus> statuses = synchronizer.sync();
if (statuses.get(temporaryId).isSuccess) {
InCountryReplicationTriggerHandler.disableTrigger = true;
// Then upsert the case in Salesforce
upsert thisCase;
// If necessary change the case Id in InCountry
String recordId = String.valueOf(thisCase.get('Id'));
if (recordId != temporaryId) {
InCountryRecordSynchronizer.migrate(JSON.serialize(synchronizer.mapSensitiveData), new Map<String, String> {
temporaryId => recordId
});
}
}
// Return the case.
return thisCase;
}
}