You need to sign in to do that
Don't have an account?
Salesforce-2-Salesforce and Lead Conversion
===================================================
S2S does not automatically keep relationships intact. There is a workaround posted here but I couldn't get it to work for lead conversion.
The problem with the above workaround is that account synchronization needs to be fully established before we can get the information we need to set up the relationships in the client org. Since account/contact/opportunities are created almost simultaneously on conversion that is no good. I tried several variations with code in one org or the other to no avail. Those three objects are simply created more quickly than the synch is completed. So I resorted to scheduled Apex.
Using scheduled Apex from a trigger has some gotchas. Job names must be unique and you can only have 20 scheduled jobs at one time. Not to mention you can't query the status of jobs by ID or name.
I use a custom object to keep track of which records still need to be synchronized and which jobs they are attached to (within some hack-y reason). You could use custom fields instead but I think the custom object is a little cleaner.
This is not fully debugged and the error handling is incomplete. I noticed that if I convert a second lead before the first one is done, I get two scheduled jobs. But that does not lead to an error so I let it be.
Hope this helps some folks out there.
================== Custom Object ==================
S2SParentPropogation__c
JobID__c, Text(18)
LocalAccountId__c, Text(18)
LocalId__c, Text(18)
Status__c, Picklist (values of Inserted, Completed, Error)
===================================================
================= Contact Trigger =================
trigger Contact_AfterInsert on Contact (after insert) {
S2S_EstablishPropogation prop = new S2S_EstablishPropogation();
prop.createContactSyncJob(Trigger.new);
}
===================================================
================= Opportunity Trigger =================
trigger Opportunity_AfterInsert on Opportunity (after insert) {
S2S_EstablishPropogation prop = new S2S_EstablishPropogation();
prop.createOpportunitySyncJob(Trigger.new);
}
=======================================================
============ S2S_EstablishPropogation Class ============
public class S2S_EstablishPropogation {
public void createContactSyncJob(List<Contact> contacts) {
// init
List<PartnerNetworkRecordConnection> newSynchRecords = new List<PartnerNetworkRecordConnection>();
List<Contact> toSynchLater = new List<Contact>();
S2S_Utils su = new S2S_Utils();
// build list of account IDs
List<ID> localAccountIds = new List<ID>();
for (Contact c : contacts) {
localAccountIds.add(c.AccountId);
}
// figure out which of the parent accounts have been synced already
List<PartnerNetworkRecordConnection> findSynchedAccounts = su.findAlreadySynchedAccounts(localAccountIds);
Set<ID> synchedLocalAccounts = buildSetOfLocalAccountIds(findSynchedAccounts);
// process those contacts who's accounts have been synced and store the rest for later
for (Contact c : contacts) {
// if the parent account has already been synched, we can save ourself some work later
if (synchedLocalAccounts.contains(c.AccountId)) {
// connection record will auto-share this contact with the client org
newSynchRecords.add(su.buildPartnerConnectionRecord(c.Id, c.AccountId));
// no joy, log this to a table to synch later
} else { toSynchLater.add(c); }
}
// insert those we can now
insert newSynchRecords;
// now create records for later syncing
if (toSynchLater.size() > 0) {
// make sure there's a job in the future to process the records we're about to create
String jobId = findOrCreateJobThatEstablishesSharesLater('003');
// finally, create record of opportunities to sync
List<S2SParentPropogation__c> deferredSyncRecords = new List<S2SParentPropogation__c>();
for (Contact c : toSynchLater) {
deferredSyncRecords.add(createSyncLaterRecord(c.Id, c.AccountId, jobId));
}
insert deferredSyncRecords;
}
}
public void createOpportunitySyncJob(List<Opportunity> oppts) {
// init
List<PartnerNetworkRecordConnection> newSynchRecords = new List<PartnerNetworkRecordConnection>();
List<Opportunity> toSynchLater = new List<Opportunity>();
S2S_Utils su = new S2S_Utils();
// build list of account IDs
List<ID> localAccountIds = new List<ID>();
for (Opportunity o : oppts) {
localAccountIds.add(o.AccountId);
}
// figure out which of the parent accounts have been synced already
List<PartnerNetworkRecordConnection> findSynchedAccounts = su.findAlreadySynchedAccounts(localAccountIds);
Set<ID> synchedLocalAccounts = buildSetOfLocalAccountIds(findSynchedAccounts);
// process those opporunities who's accounts have been synced and store the rest for later
for (Opportunity o : oppts) {
// if the parent account has already been synched, we can save ourself some work later
if (synchedLocalAccounts.contains(o.AccountId)) {
// connection record will auto-share this oppt with the client org
newSynchRecords.add(su.buildPartnerConnectionRecord(o.Id, o.AccountId));
// no joy, log this to a table to synch later
} else { toSynchLater.add(o); }
}
// insert those we can now
insert newSynchRecords;
// now create records for later syncing
if (toSynchLater.size() > 0) {
// make sure there's a job in the future to process the records we're about to create
String jobId = findOrCreateJobThatEstablishesSharesLater('006');
// create record of opportunities to sync
List<S2SParentPropogation__c> deferredSyncRecords = new List<S2SParentPropogation__c>();
for (Opportunity o : toSynchLater) {
deferredSyncRecords.add(createSyncLaterRecord(o.Id, o.AccountId, jobId));
}
insert deferredSyncRecords;
}
}
private String scheduleJob(String objectType) {
Integer rand = Crypto.getRandomInteger();
String ran = String.valueOf(rand).substring(5);
S2S_Utils su = new S2S_Utils();
String sch = su.getScheduleStringMinutesFromNow(S2S_Utils.SCHEDULE_JOB_HOW_MANY_MINUTES_IN_FUTURE);
S2S_PropogateParent prop = new S2S_PropogateParent();
return System.schedule('S2S sync-' + objectType + '-' + ran, sch, prop);
}
private Set<ID> buildSetOfLocalAccountIds(List<PartnerNetworkRecordConnection> findSynchedAccounts) {
Set<ID> synchedLocalAccounts = new Set<ID>();
for (PartnerNetworkRecordConnection a : findSynchedAccounts) {
synchedLocalAccounts.add(a.LocalRecordId);
}
return synchedLocalAccounts;
}
private String findOrCreateJobThatEstablishesSharesLater(String objectType) {
String jobId = 'addon';
// find already scehduled jobs
// we can't query directly so we have to make use of our own stored IDs
Datetime wasCreatedAtOrAfter = Datetime.now().addSeconds(30).addMinutes(-3);
List<S2SParentPropogation__c> existingJobs =
[SELECT Id FROM S2SParentPropogation__c
WHERE JobID__c != null
AND JobID__c != 'addon'
AND Status__c = 'Inserted'
AND CreatedDate > :wasCreatedAtOrAfter];
if (existingJobs.size() == 0) {
// nothing is scheduled, create a job
jobId = scheduleJob(objectType);
}
return jobId;
}
private S2SParentPropogation__c createSyncLaterRecord(ID child, ID parent, String jobId) {
return new S2SParentPropogation__c(
LocalId__c = child,
LocalAccountId__c = parent,
Status__c = 'Inserted',
JobID__c = jobId
);
}
}
====================================================
============ S2S_PropogateParent Class ============
global class S2S_PropogateParent implements Schedulable {
global void execute(SchedulableContext SC) {
S2S_Utils su = new S2S_Utils();
// clear out previously completed records
Datetime hourAgo = DateTime.now().addHours(-1);
List<S2SParentPropogation__c> deleteNow = [SELECT Id FROM S2SParentPropogation__c WHERE Status__c = 'Completed' AND CreatedDate < :hourAgo];
try {
delete deleteNow;
} catch (Exception e) {
// meh, it was probably already deleted by another job running in parallel
}
/****************** Retrieve Data **********************/
// what records are out there awaiting sync
Datetime minsAgo = DateTime.now().addMinutes(-1);
List<S2SParentPropogation__c> processNow =
[SELECT LocalId__c, LocalAccountId__c, Status__c
FROM S2SParentPropogation__c
WHERE Status__c = 'Inserted'
AND (JobID__c = 'DOH-testing' OR CreatedDate < :minsAgo)];
// get list of accounts so we know which sync records to look for
List<ID> localAccountIds = new List<ID>();
for (S2SParentPropogation__c pp : processNow) {
localAccountIds.add(pp.LocalAccountId__c);
}
// which of those accounts have completed sync?
List<PartnerNetworkRecordConnection> findSynchedAccounts = su.findAlreadySynchedAccounts(localAccountIds);
/****************** Add Sync Records **********************/
List<PartnerNetworkRecordConnection> newSynchRecords = new List<PartnerNetworkRecordConnection>();
for (S2SParentPropogation__c p : processNow) {
// find corresponding account S2S sync record
PartnerNetworkRecordConnection accountS2S = null;
for (PartnerNetworkRecordConnection pn : findSynchedAccounts) {
if (pn.LocalRecordId == p.localAccountId__c) {
accountS2S = pn;
}
}
// found it
if (accountS2S != null) {
// auto-share this contact/opportunity with the client org
newSynchRecords.add(su.buildPartnerConnectionRecord(p.LocalId__c, p.LocalAccountId__c));
p.Status__c = 'Completed';
}
}
insert newSynchRecords;
update processNow;
}
}
===================================================
================= S2S_Utils Class =================
public class S2S_Utils {
public static Integer SCHEDULE_JOB_HOW_MANY_MINUTES_IN_FUTURE = 3;
public static String CONNECTION_NAME = '[Connection Name]';
public List<PartnerNetworkRecordConnection> findAlreadySynchedAccounts(List<ID> localAccountIds) {
// find accounts that have already been synched
return [SELECT Id, ConnectionId, PartnerRecordId, LocalRecordId
FROM PartnerNetworkRecordConnection
WHERE LocalRecordId in :localAccountIds
AND (Status ='Sent' OR Status = 'Received')];
}
public PartnerNetworkRecordConnection buildPartnerConnectionRecord(ID child, ID parent) {
return new PartnerNetworkRecordConnection (
ConnectionId = getConnectionId(),
LocalRecordId = child,
ParentRecordId = parent,
SendClosedTasks = false,
SendOpenTasks = false,
SendEmails = false
);
}
public ID getConnectionId() {
List<PartnerNetworkConnection> partnerNetConList =
[SELECT id FROM PartnerNetworkConnection
WHERE connectionStatus = 'Accepted'
AND connectionName = :S2S_Utils.CONNECTION_NAME];
if (partnerNetConList.size() != 0) {
return partnerNetConList.get(0).Id;
}
return null;
}
public String getScheduleStringMinutesFromNow(Integer mins) {
Datetime runTime = Datetime.now().addMinutes(mins);
return twoDigit(runTime.second()) + ' ' + twoDigit(runTime.minute()) + ' ' + twoDigit(runTime.hour()) + ' ' + twoDigit(runTime.day()) + ' ' + twoDigit(runTime.month()) + ' ? ' + runTime.year();
}
public String twoDigit(integer raw) {
if (raw < 10 && raw > 0) {
return '0' + String.valueOf(raw);
} else {
return String.valueOf(raw);
}
}
}
I know this is a very old post , i am running into same issue what you faced and i am trying to implement this . have you had success with this? And how does the scheduling work , after i schedle the batch the Converted leads records are not going to the target Org
thanks
Akhil