Sunday, October 28, 2012

Work with Salesforce To Salesforce (S2S) in APEX.

In SF there are two objects responsible for S2S connection: PartnerNetworkConnection - keeps all information about current connection and PartnerNetworkRecordConnection - holds information about record that was forwarded. I will not write here how to create S2S connection, there is a lot of information about in internet. We will talk about forwarding objects that are already published and subscribed in both orgs. We can forward objects in two ways: manualy in page or in code. Below is sample of class to do this in code:
public class S2SService {
    public static final String CONNTECTION_STATUS_ACCEPTED = 'Accepted';
// Method return Id of current connection. 
    public static Id getConnectionId( String connectionName ) {
        return [SELECT Id 
            FROM PartnerNetworkConnection 
            WHERE connectionStatus = : CONNTECTION_STATUS_ACCEPTED 
                      AND connectionName = : connectionName 
                LIMIT 1].Id; 
    } 
/* 
@param: objectList - list of objects you want to forward; 
@param: parentRecordIdMap - Map<Record Id; Parent Id> map of Parent records;
@param: relatedRecordsMap - Map<Record Id; String with related records String>
                            map with objects API names for whom forwarding record
                            is parent and you want to forward them too; 
@param: connectionName - name of connection you want to use; 
*/ 
    public static void sendToPartnerOrg( List<sObject> objectList,
                                         Map<Id,Id> parentRecordIdMap, 
                                         Map<Id,String> relatedRecordsMap,
                                         String connectionName ){ 
        if ( objectList != null && !objectList.isEmpty() ){ 
// First thing we need to have connection name
            Id connectionId = getConnectionId( connectionName ); 
// Creating list of PartnerNetworkRecordConnection that will be inserted
        List<PartnerNetworkRecordConnection> objectConnections 
            =  new  List<PartnerNetworkRecordConnection>(); 
// Going throught list of recordth we want to share            
        for ( sObject obj: objectList ) 
// Adding new PartnerNetworkRecordConnection to list
            objectConnections.add(  
                new PartnerNetworkRecordConnection( 
// ConnectionId - setting our PartnerNetworkConnection Id
                    ConnectionId = connectionId, 
// LocalRecordId - Id of record we want to forward
                    LocalRecordId = obj.Id, 
// SendClosedTasks and SendOpenTasks false because we do not wont to forward tham too
                    SendClosedTasks = false,
                    SendOpenTasks = false,
                    SendEmails = true, 
// ParentRecordId - parent of forwarding record
                    ParentRecordId = parentRecordIdMap != null ?
                                     parentRecordIdMap.get( obj.Id ): 
                                     null,
// RelatedRecords - child objects you what to forward too
                    RelatedRecords = relatedRecordsMap != null ?
                                     relatedRecordsMap.get( obj.Id ): 
                                     null )
            ); 
// Upserting of PartnerNetworkRecordConnection list with linking error to the object
// we are forwarding 
        if ( !objectConnections.isEmpty() ) {
            try{ 
                database.upsert( objectConnections ); 
            }catch( DMLException dmlEx ){ 
                for ( Integer i = 0; i < dmlEx.getNumDml(); i++ )
                    if( dmlEx.getDmlStatusCode( i ) == 'INVALID_PARTNER_NETWORK_STATUS' ) 
                        for( PartnerNetworkRecordConnection connectionItem: objectConnections ) 
                            if( dmlEx.getDmlId( i ) == connectionItem.Id ) 
                                for( sObject obj : objectList ) 
                                    if( connectionItem.LocalRecordId == obj.Id ) 
                                        obj.addError( 'Incorrect partner network status.' );
                } 
            } 
        }  
    } 
}
Note: if there will be an error, in upserting one the record, all bunch will not be forwarded.

Also we do not need to forget about SF Governors and Limits. Because if we have triggers and some logic on forwarded records, in partner org, we can hit them. In this case we will not get error on Connection history, but all upserted PartnerNetworkRecordConnections will have status "Pending (sent)" and will not be forwarded.

To be able to forward records again we need to change status of PartnerNetworkRecordConnection from "Pending (sent)" to "Inactive". To do this we need to delete PartnerNetworkRecordConnection, but it will not be deleted, it will just change status to "Pending (sent)". After this we can insert tham again. If there will not be any errors, status will change to "Active (sent)" or "Connected" in several seconds. So if we need to change something in PartnerNetworkRecordConnection, we need to delete it and insert/upsert it again. This is strange way to work with, but it is only way.

One more thing. When we are forwarding records and filling in field ParentRecordId, we need to be sure that parent record is already forwarded, in other case we will have an error.

Useful links:


Enjoy.

13 comments:

  1. upsert doesnt works on PNRC

    ReplyDelete
    Replies
    1. Works, just try to do it. ;)

      Delete
    2. Hi Dima,

      Salesforce doent allow DML on PNRC Object!

      We get System.TypeException: DML operation UPDATE not allowed on PartnerNetworkRecordConnection

      Delete
    3. Hi, I can try to help you. Just ping me via Skype - dmitriy.e.smirnov or via gmail - dmitriy.e.smirnov@gmail.com.

      Delete
    4. Can you, please, give more information what are you trying to do?

      Delete
  2. Can we transfer records which has lookup to some other objects and still maintain the relationship with other object. Other object is already transferred. Any points would be of great help. Thanks in advance.

    ReplyDelete
    Replies
    1. If to the parent - yes. While transferring your record fill-in field called ParentRecordId on PartnerNetworkRecordConnection if you have Master-Detail relation ship.

      If you have Look up there is another way.
      On Parent object you need to set up formula field that will read its id and forward it to second org. On child record you need formula field that will have id of Look up to parent and also will be shared to another org.
      In second org you will have Trigger on before insert of child object. It will query parent object by field from first org. And fill in look up with current id of parent.

      Like this:

      Contact has look up to Account. Contact child. Account parent.

      Org 1:

      1. Account.Org1_Account_Id__c - formula field with Id of Account in org 1.
      2. Share Account with field Account.Org1_Account_Id__c. ( Also you can just share Id of Account )
      3. Contact.Account__c - Look up to Account from Contact.
      4. Contact.Org1_Account_Id__c - formula field for Look up to Account.
      5. Share Contact, with Org1_Account_Id__c field.

      Org 2:

      1. Account.Org1_Account_Id__c - Text(18) field.
      2. Subscribe in connections Account.Org1_Account_Id__c ( Org 1) > Account.Org1_Account_Id__c ( Org 2 )
      3. Contact.Account__c - Look up to Account from Contact.
      4. Contact.Org1_Account_Id__c - Text(18) field.
      5. Subscribe in connections Contact.Org1_Account_Id__c ( Org 1) > Contact.Org1_Account_Id__c ( Org 2 )
      6. Create trigger before insert:
      a. Look for all Account with Org1_Account_Id__c = Contact.Org1_Account_Id__c;
      b. Contact.Account__c = Account.Id with Org1_Account_Id__c = Contact.Org1_Account_Id__c.

      Delete
  3. This comment has been removed by the author.

    ReplyDelete
  4. nice article thanks for the information one query
    if we create a record from one org A and share it with another org B using PartnerNetworkRecordConnection the record is transferred immediately.
    No if we delete the connection from org A and make some update in the record and again share the connection using upsert a new record is connected in org B and not the previous record is updated. Is it possible to update the same old record in org B instead of new record been created.

    ReplyDelete
    Replies
    1. Maybe this is answer to your question: http://dimmys.blogspot.com/2013/07/re-connecting-records-through-s2s.html

      Delete
  5. Hey Dima ,

    I am implementing S2S and running into issues , can you look at this once if you have a chance https://developer.salesforce.com/forums?communityId=09aF00000004HMGIA2#!/feedtype=SINGLE_QUESTION_DETAIL&dc=Apex_Code_Development&criteria=ALLQUESTIONS&id=906F00000009OURIA2

    ReplyDelete
    Replies
    1. Hi Akhil, sorry for so long time response.

      As for me here are several ways of solving the problem. In any case first what you need to do is remove all inserts from for loop.
      Create one list for Account PNRC and insert it. After that connections need some time ( from 4 seconds up to 1 min ) to get connected. So I propose to create Opportunity PNRC list and insert it in @future method. If this will not help I propose to create batch that will share Opportunities after some period of time.

      Delete
  6. Hey Dima,
    I'm getting the below error while I'm trying to setup a connection..

    caused by: System.DmlException: Insert failed. First exception on row 8; first error: INVALID_PARTNER_NETWORK_STATUS, invalid status for partner network operation: []

    ReplyDelete