Sync between modules, prevent loop

So here is my dilemma.

I have the contacts module I would like to use to handle client accounts. Then I have custom modules that I would like to sync the data across on every save.

For example, If I am in Contacts module and I save a record update. I will have all my child records updated.
When I update a child module, I would like the parent record to be updated.

The custom module I am using is based of the vanilla module for module builder. The plain non fancy do it yourself module.

After some digging around I found a great way of updating child records from here.
http://greg.ambrose.id.au/2013/11/03/assigning-child-records-in-sugarcrm/

That was a HUGE help in finding a method to automatically update my child records.
Next Since both modules will have the fields named the same I wanted to create a way to update each field with a foreach loop without having to type out every single field I want to sync. This was a way to create nice lean code.

In my efforts to do so I found my after_save hook crashing. After Much testing I found out my loop was also trying to sync fields you would not want to sync, for example ID.

So I created an array of fields I would like to exclude and ended with the below code.
I am providing this since I am sure someone else could use the help.

So here we go,
spoiler are keywords for people searching for my trouble
[spoiler]sugarcrm suitecrm sync modules sync custom modules sync fields between modules sync fields with custom module prevent loop after_save hook update child records[/spoiler]


<?php
/***********************************
Project: Pipe Sync
Original Dev: John Torres, April 2014
@2014 John Torres
john.torres87@outlook.com

Desc: Sync All Relevant fields through custom modules and contacts when specific conditions are met.

The contents of this file are governed by the GNU General Public License (GPL).
A copy of said license is available here: http://www.gnu.org/copyleft/gpl.html
This code is provided AS IS and WITHOUT WARRANTY OF ANY KIND.
*************************************/
 
//prevents directly accessing this file from a web browser
if(!defined('sugarEntry') ||!sugarEntry) die('Not A Valid Entry Point');

class Reassign
{
    // all the related modules we want to update
    private $childModules = array(
        'contacts_pipe_intake_1',
        'documents',
        'quotes'
    );
	//Array of fields to exclude for sync, such as db id's schema, all objects we do not want to work with!
	private	$excludefields = array (
			status,
			assigned_user_id,
			assigned_user_name,
			assigned_user_link,
			id,
			name,
			date_entered,
			date_modified,
			modified_user_id,
			modified_by_name,
			created_by,
			created_by_name,
			description,
			deleted,
			created_by_link,
			modified_user_link,
			new_schema,
			module_dir,
			object_name,
			table_name,
			importable,
			disable_row_level_security,
			db,
			new_with_id,
			disable_vardefs,
			new_assigned_user_name,
			processed_dates_times,
			process_save_dates,
			save_from_post,
			duplicates_found,
			update_date_modified,
			update_modified_by,
			update_date_entered,
			set_created_by,
			team_set_id,
			ungreedy_count,
			module_name,
			field_name_map,
			field_defs,
			custom_fields,
			column_fields,
			list_fields,
			additional_column_fields,
			relationship_fields,
			current_notify_user,
			fetched_row,
			fetched_rel_row,
			layout_def,
			force_load_details,
			optimistic_lock,
			disable_custom_fields,
			number_formatting_done,
			process_field_encrypted,
			acltype,
			additional_meta_fields,
			special_notification,
			in_workflow,
			tracker_visibility,
			listview_inner_join,
			in_import,
			required_fields,
			added_custom_field_defs,
			acl_fields,
			logicHookDepth,
			contacts_pipe_intake_1,
			contacts_pipe_intake_1_name,
			contacts_pipe_intake_1contacts_ida,
			relDepth,
			contacts_pipe_intake_1_name_owner,
			contacts_pipe_intake_1_name_owner_mod,
			rel_fields_before_value	
		);
	
public function doReassign($bean, $event, $arguments)
    {	
		
        // ok, we need to sync fields on all child records
 
        // get the bean to load all its relationships
        $links = $bean->get_linked_fields();
		
        foreach($links as $relName => $rel)
        {
			//Comment out the below if-statement and uncomment the below error message
			//check the log to see the relationship names available.
			//$GLOBALS['log']->fatal($relName);
            // ignore relationships we dont want
            if(!in_array($relName, $this->childModules)) continue;
 
            // lets load this related record
            $ok = $bean->load_relationship($relName);
            if($ok == false) trigger_error("Cant load rel $relName", E_USER_ERROR);
 
            //get all the records for this relationship
            $relBeans = $bean->$relName->getBeans();
			
			//Work on each individual child record
            foreach($relBeans as $relBean)
            {
			
				
				//sync all the fields
				foreach($relBean as $relBeanField => $relBeanRest){
					
					//exclude fields we do not want to sync, ex: $bean->id
					if(in_array($relBeanField, $this->excludefields)) continue;
					$relBean->$relBeanField=$bean->$relBeanField;
														
				}
				//let's save the current record
				$relBean->save(false);
                
            }
        }
    }
}
?>

Then on the chidl record on an after_save hook I will have an update parent record type of code.

The issue is this. When I trigger the save from lets say Module A this will trigger a save for Module B
When the Module B save is triggered, this will trigger a save for Module A and so on until boom.

I know I can just have some property get updated to prevent a loop for example as found here.
http://support.sugarcrm.com/02_Documentation/04_Sugar_Developer/Sugar_Developer_Guide_7.1/60_Logic_Hooks/90_Examples/Preventing_Infinite_Loops_with_Logic_Hooks/

But my issue is once there are concurrent updadtes to either module they will no longer update.
So this is for some of the experienced coders out there.

What logic can I use between the records to prevent a loop. But also allow concurrent future updadtes.

For example:

Update Module A updates child records for Module B, then Stop!
Update Module B child record updates parent record Module A then Stop!
Then when the same records update again allow for future syncs between records without a loop.

I just cant thing of any logic to do this, maybe I am simply tired.

A push in the right direction would be HUGE.

I was thinking of something like

Module A has variable $x
Module B has variable $y

When A is saved x= x+1
triggering a B save and y=y+1
if x=y then stop
But then what do i do for future updates to the record?
If I try lets say, when a save occurs, increase x+1
if x>y then :save
but that would cause a loop

I’m confused.

What would be a good solution to this?

I would like to accomplish this without having to resort to accessing the database with a query method.

Hi,

The approach I use is similar to the one you linked, basically setting a flag against the bean. So in the doReassign method I’d just set a flag before saving:


    //let's save the current record
    $relBean->inSync = true;
    $relBean->save(false);

Then at the start of doReassign we can prevent any of the logic from running:

    if(!empty($bean->inSync)){
        //Already running a logic hook. Exit.
        return;
    }

This is safe from concurrent updates because it’s just a flag in memory on the bean so updates by other users will still work as expected.

Thanks,
Jim

I wasn’t really sure what you meant, If I were to use this method, If i wanted to update module B in the future, those updates would not be saved to Module A?

I did find a solution, I had to resort to saving the data to the DB
AND as I was finishing up my code I realized, all I have to do is on Opening the record, pull the data from the parent record.
On saving a record, save the data to the parent record. I don’t know why I was over complicating it. This way the data is In one place and I dont have to over complicate. The reason I am using my custom modules is to have a series of task created when a contact is created.

We need to process each client individually and go through complicated steps with them. So I am using the custom modules for each step of the process, and these child records can be assigned to various employees to go through the cue on that specific Action and follow through with it.

One example is Intake, we sign up the client and the sales agent is finished, next we need to follow through with the client and verify their information, and it helps keep closes. So now we have a department that handles the back end form sales, all they have to do is look through the intake list, and do new intakes. Simple! I could also do it by just having a field with status, but it helps due to the complicated nature, it will only show the account data needed for that step and allow the employee to work quickly by going record to record quickly.

And that’s it. I would have both modules sync and all their changes synced, and I run checks to see if it needs to be updated upon opening to stop excess code form running.