Coder Social home page Coder Social logo

bulkimport's Introduction

BulkImport Upsert API

The goal of this sample code is to be able to quickly create and/or update big amounts of data, by leveraging only REST API for integration and initial data load purposes.
The code implements a POST based Bulk Upsert API for Sugar, that completes SQL lookups based on external system's unique keys and inserts or updates (upserts) multiple beans per HTTP request at once.

Please note that this sample code is not provided as an installable module, as extensive configuration is required (including creation of external data key fields) on a case by case scenario.

Requirements

  • Sugar 7.9 and above (it leverages database prepared statements syntax)

Installation

  • Deploy all provided files/folders within the custom Sugar folder
  • Run a quick repair and rebuild for the custom API to be available on the system
  • Create custom files/logic for module level special mappings after and before the save is processed wherever required (some samples are provided for Accounts, Contacts and Users on the current code)
  • Create valid config_override.php settings to enable the custom API on a module per module level

Note that when the custom fields for the external keys are created (eg: ext_key_c), there has to be the matching index (eg: CREATE INDEX idx_users_ext_key ON users_cstm (ext_key_c); ) or the performance will terribly suffer while completing the lookups.

Sample config_override.php

$sugar_config['bulk_import_settings']['modules']['Users']['sugar_key_field'] = 'ext_key_c';
$sugar_config['bulk_import_settings']['modules']['Users']['external_key_field'] = 'external_key';
$sugar_config['bulk_import_settings']['modules']['Users']['sql_query'] = 'select id_c from users_cstm where ext_key_c = ?';
$sugar_config['bulk_import_settings']['modules']['Accounts']['sugar_key_field'] = 'ext_key_c';
$sugar_config['bulk_import_settings']['modules']['Accounts']['external_key_field'] = 'external_key';
$sugar_config['bulk_import_settings']['modules']['Accounts']['sql_query'] = 'select id_c from accounts_cstm where ext_key_c = ?';
$sugar_config['bulk_import_settings']['modules']['Contacts']['sugar_key_field'] = 'ext_key_c';
$sugar_config['bulk_import_settings']['modules']['Contacts']['external_key_field'] = 'external_key';
$sugar_config['bulk_import_settings']['modules']['Contacts']['sql_query'] = 'select id_c from contacts_cstm where ext_key_c = ?';
$sugar_config['bulk_import_settings']['relationships']['Accounts']['contacts']['external_key_field_left'] = 'left_external_key';
$sugar_config['bulk_import_settings']['relationships']['Accounts']['contacts']['external_key_field_right'] = 'right_external_key';
$sugar_config['bulk_import_settings']['modules']['Documents']['sql_query'] = 'select id_c from documents_cstm where ext_key_c = ?';
$sugar_config['bulk_import_settings']['modules']['Documents']['sugar_key_field'] = 'ext_key_c';
$sugar_config['bulk_import_settings']['modules']['Documents']['external_key_field'] = 'external_key';
$sugar_config['bulk_import_settings']['modules']['Documents']['custom_before_save']['file'] = 'custom/modules/Documents/DocumentsBulkImport.php';
$sugar_config['bulk_import_settings']['modules']['Documents']['custom_before_save']['class'] = 'DocumentsBulkImport';
$sugar_config['bulk_import_settings']['relationships']['Documents']['accounts']['external_key_field_left'] = 'left_external_key';
$sugar_config['bulk_import_settings']['relationships']['Documents']['accounts']['external_key_field_right'] = 'right_external_key';

config_override.php options explanation

$sugar_config['search_engine']['force_async_index'] = true; // OPTIONAL - Core Sugar configuration option to force Elasticsearch indexing to happen in the background, asynchronously, improving record save's speed

$sugar_config['bulk_import_settings']['modules'][<sugar module name>]['sugar_key_field'] = 'ext_key_c'; // OPTIONAL - sugar field name where the external key value will be stored for the module
$sugar_config['bulk_import_settings']['modules'][<sugar module name>]['external_key_field'] = 'external_key'; // REQUIRED - REST payload variable containing the external system unique key's value
$sugar_config['bulk_import_settings']['modules'][<sugar module name>]['sql_query'] = 'select id_c from accounts_cstm where ext_key_c = ?'; // REQUIRED - Sugar database sql query that will be executed for every record, to verify if the key passed on the payload exists already in the Sugar database or not, and also to find records for relationships linking

$sugar_config['bulk_import_settings']['modules'][<sugar module name>][<custom_after_save or custom_before_save>]['file'] = 'custom/modules/Accounts/AccountsBulkImport.php'; // OPTIONAL - path of custom file to load for the module
$sugar_config['bulk_import_settings']['modules'][<sugar module name>][<custom_after_save or custom_before_save>]['class'] = 'AccountsBulkImport'; // OPTIONAL - class to instantiate

$sugar_config['bulk_import_settings']['relationships'][<sugar module name>][<sugar link field of relationship>]['external_key_field_left'] = 'left_external_key'; // REQUIRED - external key for the main module
$sugar_config['bulk_import_settings']['relationships'][<sugar module name>][<sugar link field of relationship>]['external_key_field_right'] = 'right_external_key'; // REQUIRED - external key for the related module

Important Notes

  • The API is only accessible to Admin Sugar users by design
  • The API is only available for modules specifically configured on config_override.php
  • When writing queries, do not filter out deleted records. The system will restore records as needed to prevent the otherwise possible unique key duplication
  • When writing queries, the system expects to select only one field, either the id or id_c of the record, depending on the unique key location (core or custom table). In general a query would always look like the following example: select <id or id_c> from <tablename> where <external key field> = ? but there could be exceptions where joins and more complex queries are required
  • The sugar_key_field config option could seem redundant, given that the sql query needs to be configured as well. In reality the sugar_key_field is used to make sure that the external_key value is set on the sugar_key_field field on the relevant object, so that it can be used later on for lookups leveraging the sql_query provided. The code will work also WITHOUT configuring sugar_key_field, but ONLY IF the external_key passed to the API as payload, matches the destination field where the external key will be stored in Sugar
  • SQL queries lookups leverage prepared statements, so the question mark symbol ? is required on the SQL syntax on config_override.php
  • Best practice is to use an additional unique key field, with additional indexes (not the Sugar guid id field). If it is required to use the Sugar guid id field, leverage Sugar generated guids to populate the records correctly and not some other strings
  • The system can be extended with its own hooks (before save and after save) when using the custom API (so no application logic hooks needed). The available config keys are custom_before_save and custom_after_save. The options that can be set are the file and the class. The method that will be called are either: callCustomBeforeSave or callCustomAfterSave
  • To improve performance the activity stream record creation has been disabled and the tracker functionality paused when using the bulk API
  • To improve performance further (and reduce server utilisation), make sure Elastic indexing does not happen synchronously with the config_override option provided $sugar_config['search_engine']['force_async_index'] = true;
  • It is possible to upsert one record at the time only, by passing an array of records with only one record on the array. It is not recommended, as higher throughput can be achieved by passing multiple records
  • It is possible to pass the parameter "skipUpdate":true on the REST request to prevent any record updates (including if the matching record is deleted). This is useful if as an example the initial data load was interrupted mid way. This option would allow to quickly resume the inserting of records without processing any update of previously created records. This additional parameter does not apply to relationships but only to bean's records
  • Do not pass too many records at once to the API. Make sure every HTTP response does not take more than 15-20 seconds and monitor carefully the overall infrastructure load and the API response times
  • A maximum amount of records is configured by default to 100. It can be configured through the config_override.php option $sugar_config['bulk_import_settings']['max_records']
  • It is possible to impersonate another user for all the updates of the same request by passing on the payload the parameter save_as_user_id as the relevant user's guid
  • For every record, it is possible to pass the list of its team guids separated by | with no added spaces on the field team_list. The first team in the list will be the primary Team. This functionality will only work correctly if all the Teams with the matching guids exist within the instance
  • For every record, it is possible to populate date_modified and date_entered, as long as the date formats are correct (database format in GMT eg:2013-02-27 19:56:00)
  • For every record, it is possible to populate modified_user_id, created_by, or based on external keys lookup with external_modified_user_key, external_created_user_key provided that the Users sugar guid exists and user impersonation is not active
  • For every record, it is possible to populate assigned_user_id or based on external keys lookup with external_assigned_user_key provided that the Users sugar guids exist

API Call Examples

Users (/rest/v10/BulkImport/records/Users)

{
    "records": [
        {
            "id": "47fecf72-b195-11e8-a6e1-06cd403c41f6",
            "first_name": "Test3",
            "last_name": "Test3",
            "user_name": "test3",
            "password": "test123",
            "email1": "[email protected]",
            "status": "Active",
            "employee_status": "Active",
            "UserType": "RegularUser"
        },
        {
            "id": "47fed0f8-b195-11e8-9dde-06cd403c41f6",
            "first_name": "Test2",
            "last_name": "Test2",
            "user_name": "test2",
            "password": "test123",
            "email1": "[email protected]",
            "status": "Active",
            "employee_status": "Active",
            "UserType": "RegularUser"
        }
    ]
}

Teams (/rest/v10/BulkImport/records/Teams)

{
    "records": [
        {
            "name": "Special Team 1",
            "external_key": "47fed1e8-b195-11e8-ccda-06cd403c41f6"
        },
        {
            "name": "Special Team 2",
            "external_key": "47fed1e8-b195-11e8-ccdb-06cd403c41f6"
        }
    ]
}

Users Teams relationships (/rest/v10/BulkImport/relationships/Teams/users)

{
    "records": [
        {
            "right_external_key": "test2",
            "left_external_key": "47fed1e8-b195-11e8-ccda-06cd403c41f6"
        },
        {
            "right_external_key": "test3",
            "left_external_key": "47fed1e8-b195-11e8-ccda-06cd403c41f6"
        },
        {
            "right_external_key": "test2",
            "left_external_key": "47fed1e8-b195-11e8-ccdb-06cd403c41f6"
        }
    ]
}

Accounts (/rest/v10/BulkImport/records/Accounts)

{
    "save_as_user_id": "47fed0f8-b195-11e8-9dde-06cd403c41f6",
    "records": [
        {
            "phone_office": "1234",
            "name": "a1",
            "external_key": "47fed1e8-b195-11e8-bbd3-06cd403c41f6",
            "external_assigned_user_key": "test3"
        },
        {
            "phone_office": "6789",
            "name": "a2",
            "external_key": "47fed21a-b195-11e8-87c3-06cd403c41f6",
            "external_assigned_user_key": "test3"
        }
    ]
}

Contacts (/rest/v10/BulkImport/records/Contacts)

{
    "records": [
        {
            "first_name": "c1",
            "last_name": "c1",
            "external_key": "47fed2b0-b195-11e8-865f-06cd403c41f6",
            "external_assigned_user_key": "test2",
            "date_entered": "2020-01-27 19:56:00",
            "date_modified": "2020-01-27 20:00:00",
            "modified_user_id": "47fed0f8-b195-11e8-9dde-06cd403c41f6",
            "created_by": "47fed0f8-b195-11e8-9dde-06cd403c41f6",
            "team_list": "47fed1e8-b195-11e8-ccda-06cd403c41f6|1"
        },
        {
            "first_name": "c2",
            "last_name": "c2",
            "external_key": "47fed2d8-b195-11e8-a8e5-06cd403c41f6",
            "external_assigned_user_key": "test2",
            "date_entered": "2020-01-27 19:56:00",
            "date_modified": "2020-01-27 20:00:00",
            "modified_user_id": "47fed0f8-b195-11e8-9dde-06cd403c41f6",
            "created_by": "47fed0f8-b195-11e8-9dde-06cd403c41f6",
            "team_list": "47fed1e8-b195-11e8-ccda-06cd403c41f6|1"
        }
    ]
}

Accounts Contacts relationships (/rest/v10/BulkImport/relationships/Accounts/contacts)

{
    "records": [
        {
            "right_external_key": "47fed2b0-b195-11e8-865f-06cd403c41f6",
            "left_external_key": "47fed1e8-b195-11e8-bbd3-06cd403c41f6"
        },
        {
            "right_external_key": "47fed2d8-b195-11e8-a8e5-06cd403c41f6",
            "left_external_key": "47fed1e8-b195-11e8-bbd3-06cd403c41f6"
        }
    ]
}

Cases (/rest/v10/BulkImport/records/Cases)

{
    "skipUpdate": true,
    "records": [
        {
            "name": "case1",
            "external_key": "47fed33c-b195-11e8-b939-06cd403c41f6",
            "external_assigned_user_key": "test3",
            "external_modified_user_key": "test3",
            "external_created_user_key": "test3",
            "date_entered": "2020-01-27 19:56:00",
            "date_modified": "2020-01-27 20:00:00",
            "team_list": "1|47fed1e8-b195-11e8-ccda-06cd403c41f6"
        },
        {
            "name": "case2",
            "external_key": "47fed36e-b195-11e8-b123-06cd403c41f6",
            "external_assigned_user_key": "test2",
            "external_modified_user_key": "test2",
            "external_created_user_key": "test2",
            "date_entered": "2020-01-27 19:56:00",
            "date_modified": "2020-01-27 20:00:00",
            "team_list": "1|47fed1e8-b195-11e8-ccda-06cd403c41f6"
        }
    ]
}

Cases Accounts relationships (/rest/v10/BulkImport/relationships/Cases/accounts)

{
    "records": [
        {
            "left_external_key": "47fed36e-b195-11e8-b123-06cd403c41f6",
            "right_external_key": "47fed21a-b195-11e8-87c3-06cd403c41f6"
        },
        {
            "left_external_key": "47fed33c-b195-11e8-b939-06cd403c41f6",
            "right_external_key": "47fed1e8-b195-11e8-bbd3-06cd403c41f6"
        }
    ]
}

Documents (/rest/v10/BulkImport/records/Documents)

{
    "records": [
        {
            "id": "4746a494-4e7c-11ea-9ce0-0242ac1c0005",
            "document_name": "document.pdf",
            "external_key": "1",
            "doc_type": "Sugar",
            "active_date": "2018-11-23",
            "exp_date": "2020-12-31"
        },
        {
            "id": "4748f078-4e7c-11ea-8dc6-0242ac1c0005",
            "document_name": "d2.jpg",
            "external_key": "2",
            "doc_type": "External",
            "doc_url": "https://location/d2.jpg"
        }
    ]
}

Documents Accounts relationships (/rest/v10/BulkImport/relationships/Documents/accounts)

{
  "records":[
    {
        "left_external_key":"1",
        "right_external_key":"47fed1e8-b195-11e8-bbd3-06cd403c41f6"
    },
    {
        "left_external_key":"2",
        "right_external_key":"47fed21a-b195-11e8-87c3-06cd403c41f6"
    }
  ]
}

Notes on Documents

The API doesn't support loading document records directly. Rather for performance reasons we create the Document record and required version. This allows for fast processing when large amounts of Document records need to be migrated.

The API response includes the revision ID which will need to be used to rename the source file. This file needs to be placed in the upload directory of the instance at which time it will be available in the CRM instance. Alternatively it is possible to provide a pre-generated Sugar guid to the id field as on the above example.

For On-Premise installations these files can be copied directly to the upload directory via terminal commands or SFTP.

For SugarCloud installations there are 2 methods for transferring the actual Document files.

  1. The recommended approach is to create a support ticket with SugarCRM to allow for uploading the documents to an FTP site. Note the files will still need to be in one folder with all the files stored in their Document GUID format. Additionally, this method should not be used for ongoing integrations but only for initial migrations for new instances.

  2. For moderate numbers and sizes of files using one or more module loader packages is a convenient method to transfer the files. Note that each package compressed zip would have to be limited to the upload file max size of the instance. Typically this value is 32MB See knowledge base for reference. See example below for manifest and see this page for more info on creating a module loader package.

 'copy' => array(
        array(
            'from' => '<basepath>/upload/4746a494-4e7c-11ea-9ce0-0242ac1c0005',
            'to' => 'upload/4746a494-4e7c-11ea-9ce0-0242ac1c0005'
        ),

    ),

For Documents that are stored outside of the CRM, populate the parameter doc_url with the url of the Document record and include the parameter 'doc_type' to be some value other than 'Sugar'.

Mixed response. Successful create, successful update and an error

{
  "records":[
    {
        "phone_office":"1234",
        "external_key":"a1"
    },
    {
        "phone_office":"6789",
        "external_key":"a3"
    },    
    {
        "phone_office":"6789",
        "external_key":""
    }
  ]
}
{
  "count": {
    "created": 1,
    "updated": 1,
    "errors": 1
  },
  "list": {
    "created": [
      {
        "external_key": "a3",
        "sugar_id": "aa095c18-3ec9-11e7-b0a0-37d830e68126"
      }
    ],
    "updated": [
      {
        "external_key": "a1",
        "sugar_id": "4b17a3a0-3ec8-11e7-8bdd-7a42aa821c97"
      }
    ],
    "errors": [
      {
        "message": "Module Accounts key: external_key empty"
      }
    ]
  }
}

Successful creation of records, with GUID stored on the Sugar id field, but generated outside Sugar. Skipping of updates implemented

{
  "skipUpdate":true,
  "records":[
    {
        "name":"c1",
        "id":"47fee372-b195-11e8-83be-06cd403c41f6"
    },
    {
        "name":"c2",
        "id":"47fee3a4-b195-11e8-bf63-06cd403c41f6"
    }
  ]
}
{
  "count": {
    "created": 2
  },
  "list": {
    "created": [
      {
        "external_key": "47fee372-b195-11e8-83be-06cd403c41f6",
        "sugar_id": "47fee372-b195-11e8-83be-06cd403c41f6"
      },
      {
        "external_key": "47fee3a4-b195-11e8-bf63-06cd403c41f6",
        "sugar_id": "47fee3a4-b195-11e8-bf63-06cd403c41f6"
      }
    ]
  }
}
{
  "count": {
    "warnings": 2
  },
  "list": {
    "warnings": [
      {
        "external_key": "47fee372-b195-11e8-83be-06cd403c41f6",
        "sugar_id": "47fee372-b195-11e8-83be-06cd403c41f6",
        "message": "Module Cases update skipped as requested"
      },
      {
        "external_key": "47fee3a4-b195-11e8-bf63-06cd403c41f6",
        "sugar_id": "47fee3a4-b195-11e8-bf63-06cd403c41f6",
        "message": "Module Cases update skipped as requested"
      }
    ]
  }
}

Example of relationship API call with mixed response

{
  "records":[
    {
        "left_external_key":"47fee3a4-b195-11e8-bf63-06cd403c41f6",
        "right_external_key":"a3"
    },
    {
        "left_external_key":"47fee372-b195-11e8-83be-06cd403c41f6",
        "right_external_key":"invalid-id"
    }
  ]
}
{
  "count": {
    "related": 1,
    "errors": 1
  },
  "list": {
    "related": [
      {
        "external_key_left": "47fee3a4-b195-11e8-bf63-06cd403c41f6",
        "sugar_id_left": "47fee3a4-b195-11e8-bf63-06cd403c41f6",
        "external_key_right": "a3",
        "sugar_id_right": "4b1ceac2-3ec8-11e7-8561-49cc98a30472"
      }
    ],
    "errors": [
      {
        "external_key_left": "47fee372-b195-11e8-83be-06cd403c41f6",
        "sugar_id_left": "47fee372-b195-11e8-83be-06cd403c41f6",
        "external_key_right": "invalid-id",
        "sugar_id_right": ""
      }
    ]
  }
}

Contributing

Everyone is welcome to contribute to this project! If you make a contribution, then the Contributor Terms apply to your submission.

Please check out our Contribution Guidelines for helpful hints and tips that will make it easier for us to accept your pull requests.


Copyright (c) 2018 SugarCRM Inc. Licensed by SugarCRM under the Apache 2.0 license.

bulkimport's People

Contributors

esimonetti avatar shadmickelberry avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bulkimport's Issues

SugarCloud

We have 1,000,000 accounts to load into SugarCloud. When will this be available for SugarCloud?

Support for multiple external key configuration

Although rare I've run into cases where there are more than one field used as a unique search criteria. An example of this is for Quote Line items that do not have a key but are referenced by an order ID and sequence or line number. This is usually a limitation of the external system requiring a work around.

I have made the fork and am going to take a shot at implementing this but any commentary is welcome.

Email Address Handling

Hi there,

Try to import Leads with email address and encounter an issue. The lead records and the email address records are both created. However, they are not linked to each other.

Try using email1 field as well as an email array but both options do not work. Am I missing something?

Thanks in advance.

Bulk Document Importing Support

I have used the basis of the Bulk Importer to assist in importing and relating Document records. Since this is often a common issue that comes up when importing new data it would be nice to implement this and make it compatible with On-Demand.

Perhaps this would need to be run on a local environment then package up the changes and open a support ticket to move a compressed version loaded via FTP. I'd like to help contribute to this functionality but perhaps we can have some discussion on the best method to implement.

Thank you

Module not allowed

I am call the "/rest/v11/BulkImport/records/av5_circuits" endpoint with this body
{"records":[{"name":"test"}]}
and a valid oauth-token but I get this error back.
{ "error": "invalid_parameter", "error_message": "Module av5_circuits not allowed" }
I have created the module in sugar and verified the name is correct. I am wondering if this issue is in SugarCRM or in this API?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.