Friday, March 30, 2012

Logic Hooks: Lead History Transfer

Have you noticed what happens to history information associated with a lead when one converts that lead into a contact within SugarCRM? 

If not, give it a try. Enter a lead record named John Doe, add a Note under its History section. Next, convert the lead into a contact. Now, take a look at the History section on the new contact record for John Doe that was created via the conversion process and compare it to the History section on the lead record. You will notice that the Note entry is nowhere to be found on the contact record and instead, remains associated with the lead record.

This separation allows us to see which interactions were completed when the individual was a lead and which were completed after it was converted into a contact. In turn, it helps us identify processes and techniques that led us to converting it. 

All future activity relating to that individual would be recorded against the contact record, not the lead. However, many users find that the distinction between lead and contact history causes too much confusion.

One problem that is created by this separation of data is that looking at the History section of a contact does not give us a full picture of everything that has occurred over time, as it relates to the individual. One must toggle between the contact and lead record in order to gather that insight. 

How can we address this problem?

One school of thought would bring us to the conclusion that one should not use the Leads module at all and simply enter everyone as a contact. This would solve the History problem, but creates others, such as the inability to track lead conversion rates. 

A second approach would be to carry over the History data from the lead over to the contact record during the conversion. Doing so would solve the problem as all the History would appear on the contact record, but a facility for this does not exist in SugarCRM. Fortunately, it can be easily accomplished via a logic hook.

Let us take a look at the logic hook we would use.

To begin with, we need the standard logic hook definition which belongs in our logic_hooks.php file. In this case, it will be defined as a before_save hook, with the following code:


$hook_version = 1; 
$hook_array = Array(); 
$hook_array['before_save'] = Array(); 
$hook_array['before_save'][] = Array(1, 'DataTransfer', 'custom/modules/Leads/DataTransfer.php','DataTransfer', 'doDataTransfer'); 


Alright, but where do we place this file? 

After all, when one converts a lead, more than one module is involved in the process. For example, the process creates a contact and an account, in addition to providing options for creating an opportunity, call and other type of records.

As it turns out, the best place for our logic hook is within the Leads module, thus, we will place the logic_hooks.php file in the custom/modules/Leads folder.

Lastly, we need to create the custom code file that performs the action of unlinking the History entry from the lead record and then links it to the contact. We have named the file DataTransfer.php within our logic hook definition, so this is the name we will use. Here is the content of DataTransfer.php:


Project: Relation Transfer
Original Dev: Angel Magaña, March 2012
@2012 Angel Magaña

Desc: Logic Hook for Transferring Relation

The contents of this file are governed by the GNU General Public License (GPL).
A copy of said license is available here:
This code is provided AS IS and WITHOUT WARRANTY OF ANY KIND.

class DataTransfer
function doDataTransfer($bean, $events, $arguments)
$action = $_REQUEST['action'];
if ($action == 'ConvertLead') //Must confirm it only triggers on conversion!!
$lead_id = $bean->id;
$contact_id = $bean->contact_id;
//Remove relationship 
$notes = array();
foreach ($bean->notes->getBeans() as $note)
$note_id = $note->id;
$notes[] = $note_id;
$bean->notes->delete($lead_id, $note_id);
//Transfer relationship to Contact record
$contact = new Contact();
foreach ($notes as $note)


Note that for the purposes of our example, the code has been simplified a bit so as to only transfer Notes entries. In addition, notice the following lines from above:

$action  = $_REQUEST['action'];
if ($action == 'ConvertLead')

Because the logic hook is defined to trigger upon a save occurring on a lead record, we must be careful to not execute the code, and in turn unlink the Notes records, every time the save button is clicked on a lead. To do this, we must explicitly limit the transfer of the Notes data to only those situations where the save is the result of a conversion. The above referenced lines are used to confirm that by first obtaining the action that is occurring within SugarCRM and secondly, verifying it is the ConvertLead action, which is the one associated with the process we are concerned with.

We will want to place this file in the custom/modules/Leads folder as well. 

Once in place, give it a try by converting a lead with a related note entry in its History section and then check the resulting contact record. 


  1. This comment has been removed by the author.

  2. Hi,
    Love your articles, starting a career with SugarCRM and I find your articles very helpful.

    I used the same method as described here and an older post by you to implement a logic hook onto a deployed custom module that was created in the module builder.

    The purpose of it is to add a date and time to when in was last modified in the "description" field.

    my logic_hook.php is this

    $hook_version = 1;

    $hook_array = Array();

    // position, file, function

    $hook_array['before_save'] = Array();

    $hook_array['before_save'][] = Array(1, 'AddTimeStamp', 'custom/modules/soh_Media_Inquiries/AddTimeStamp.php ','AddTimeStamp', 'StampIt');

    and AddTimeStamp.php is:

    //prevents directly accessing this file from a web browser

    if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

    class AddTimeStamp {

    function StampIt(& $focus, $event){

    global $current_user;

    $focus->description .= “\nSaved on ”. date(“Y-m-d g:i a”). “ by ”. $current_user->user_name;



    I have placed both of the newly created files in /custom/modules/soh_Media_Inquiries/

    Help would highly appreciated

    P.S. i'm using Sugar on a a linux VM

    1. Hi,

      Thanks for the feedback, I am happy to hear you have found it helpful.

      You don't specify what it is you are having a problem with, but this line might be a problem:

      function StampIt(& $focus, $event){


      function StampIt($bean, $events, $arguments) {

      and consequently, $focus->description should be changed to $bean->description.

      Please elaborate on your problem if you require further assistance.

  3. thank you that fixes it :)

    could you tell me the difference between using $focus and $bean ?

    off-topic: could you point me towards a more in-depth and example based resource/meta-tutorial/book that is updated for v6.4 or 6.5 of sugar CRM, as some of the examples in the official developer guide are not working even when using the same version as the guide is, besides that the developer guide is lacking in other areas such as blurred diagrams and more focus on extending Sugar without giving a solid understanding in its actual working.

    1. Cool.

      I am not entirely sure what the differences are between the two. I've never compared them, but based on my usage of both, it seems they are very similar. However, they are used in different context. As you noticed, $focus doesn't work in a logic hook, but would work in other areas and vice-versa.

      As for documentation/examples, the only suggestion I can give you is to check out the developer site and blog:

  4. I'm trying to copy an opportunity field to a project field as soon as the project is created. I'm doing an After_Save and I'm stuck on getting the ID from the relationship so I can set the field. Your assistance would be greatly appreciated.

    function CopyFields(&$bean, $event, $arguments) {
    global $current_user;
    //GET Project-ID

    $projectID = $bean->id;

    //Get Opportunity-ID
    $bean->load_relationship('Project'); //Also tried projects_opportunities

    $opportunityID = implode($bean->projects_opportunities->get());

    //GET Opportunity-ID-BEAN
    $opportunity = new Opportunity();

    //Set the Project Fields from the Opportunity Fields

    $bean->number_of_ac_units_c = $opportunity->no_of_ac_units_c;


    1. Hmmm...

      I am a bit confused about what it is you are trying to do. One doesn't have the ability to link an opportunity to a project until after the project has already been added and the DetailView is displayed.

      You could execute an after_relationship_add logic hook (triggered when Opportunity is linked via subpanel) and you should be able to grab the opportunity id via $bean->opportunity_id and then load the opportunity record, read the field and update the project.

      Alternatively, calling getBeans(), as illustrated in this article should also give you the opportunity ID values.

  5. Hey there,

    Wanted your help again.

    I've created a 1-M relationship Primary Module = Contacts; Related Module = Leads.

    This creates a sub-panel in Contacts from Leads with Contacts as a field in Leads.
    I wanted to move this field to Contacts when the Lead is converted.

    There are the foll. tables that get created.

    | Field | Type | Null | Key | Default | Extra |
    | id | varchar(36) | NO | PRI | NULL | |
    | date_modified | datetime | YES | | NULL | |
    | deleted | tinyint(1) | YES | | 0 | |
    | contacts_leads_1contacts_ida | varchar(36) | YES | MUL | NULL | |
    | contacts_leads_1leads_idb | varchar(36) | YES | MUL | NULL | |


    desc contacts_contacts_1_c;
    | Field | Type | Null | Key | Default | Extra |
    | id | varchar(36) | NO | PRI | NULL | |
    | date_modified | datetime | YES | | NULL | |
    | deleted | tinyint(1) | YES | | 0 | |
    | contacts_contacts_1contacts_ida | varchar(36) | YES | MUL | NULL | |
    | contacts_contacts_1contacts_idb | varchar(36) | YES | MUL | NULL | |

    So basically, on conversion, contacts_leads_1contacts_ida needs to move over to contacts_contacts_1contacts_idb

    Thanx in advance.

  6. Hola Angel,

    Este Logic Hook funciona en la versión Enterprise, Version

    1. Hola,

      Seguro requiere algunos ajustes debido a que en 7.x no existe el parametro 'action' que se utiliza para reconocer el momento cuando se convierte el Lead.

  7. Hello

    can anyone help me please?

    I would like to add new one-to-many relationship from the lead page to my custom module.
    As I understand it, I have to use a logical hook, but I cannot implement it.
    Could you help me?
    I have a lot of difficulties with this, for example, how can I get the current ID? or is it not needed at all
    Here is an example of my use case:
    I would like to have on the lead page such fields as contact information with two prefixes home and work.
    And so that the field with the home prefix after conversion of the lead is automatically recorded as a record of a one-to-many relationship in my custom module. And fields with a worker prefix will be created in another one-to-many relationship record.
    I would like a new record to be created for each prefix.
    For example, for each fields, home mobile, work mobile, instagram, email, work email, so that all these fields would be as separate records in the subpanel of my custom module. (one-to-many relationship)

    I hope I was able to explain it correctly.
    I'm not very good at this, I wanted to ask you for advice, maybe someone came across a similar one and could share an example of my code that I could change


    1. You may want to consider contracting a consultant to do this work for you, if you are not comfortable doing it yourself.

      A logic hook seems like the simplest approach to addressing your specific need, but does require some expertise with the Sugar Framework and PHP programming in general.

      For one, the logic itself would need to account for the various field types and what they translate to within your custom module(s).


Your comments, feedback and suggestions are welcome, but please refrain from using offensive language and/or berating others. Thank you in advance.